Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
50 changes: 50 additions & 0 deletions docs/core/tracing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

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<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

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.
50 changes: 50 additions & 0 deletions docs/utilities/serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -472,3 +472,53 @@ to powertools.You can then use it to do your validation or in idempotency module
}
}
```

## Advanced

### Lambda SnapStart priming

The Serialization 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 `JsonConfig` 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"}.

If you don't set a custom `JsonConfig` in your code yet, make sure to reference `JsonConfig` 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.utilities.JsonConfig;
import static software.amazon.lambda.powertools.utilities.EventDeserializer.extractDataFrom;

public class MyFunctionHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

public MyFunctionHandler() {
JsonConfig.get(); // Ensure JsonConfig is loaded for SnapStart
}

@Override
public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) {
Product product = extractDataFrom(input).as(Product.class);
// ...
return something;
}
}
```

=== "Static Initializer"

```java hl_lines="7"
import software.amazon.lambda.powertools.utilities.JsonConfig;
import static software.amazon.lambda.powertools.utilities.EventDeserializer.extractDataFrom;

public class MyFunctionHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

static {
JsonConfig.get(); // Ensure JsonConfig is loaded for SnapStart
}

@Override
public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) {
Product product = extractDataFrom(input).as(Product.class);
// ...
return something;
}
}
```
31 changes: 31 additions & 0 deletions powertools-serialization/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.crac</groupId>
<artifactId>crac</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.lambda</groupId>
<artifactId>powertools-common</artifactId>
</dependency>

<!-- Test dependencies -->
<dependency>
Expand Down Expand Up @@ -74,6 +82,11 @@
<artifactId>aws-lambda-java-tests</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand All @@ -96,6 +109,24 @@
</build>

<profiles>
<profile>
<id>generate-classesloaded-file</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
-Xlog:class+load=info:classesloaded.txt
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.lang=ALL-UNNAMED
</argLine>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>generate-graalvm-files</id>
<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,30 @@
import io.burt.jmespath.function.FunctionRegistry;
import io.burt.jmespath.jackson.JacksonRuntime;
import java.util.function.Supplier;
import org.crac.Context;
import org.crac.Core;
import org.crac.Resource;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;
import com.amazonaws.services.lambda.runtime.events.ActiveMQEvent;
import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent;
import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent;
import com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent;
import com.amazonaws.services.lambda.runtime.events.KafkaEvent;
import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsFirehoseInputPreprocessingEvent;
import com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsStreamsInputPreprocessingEvent;
import com.amazonaws.services.lambda.runtime.events.KinesisEvent;
import com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent;
import com.amazonaws.services.lambda.runtime.events.RabbitMQEvent;
import com.amazonaws.services.lambda.runtime.events.SNSEvent;
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
import com.amazonaws.services.lambda.runtime.events.ScheduledEvent;
import software.amazon.lambda.powertools.common.internal.ClassPreLoader;
import software.amazon.lambda.powertools.utilities.jmespath.Base64Function;
import software.amazon.lambda.powertools.utilities.jmespath.Base64GZipFunction;
import software.amazon.lambda.powertools.utilities.jmespath.JsonFunction;

public final class JsonConfig {
public final class JsonConfig implements Resource {

private static final Supplier<ObjectMapper> objectMapperSupplier = () -> JsonMapper.builder()
// Don't throw an exception when json has extra fields you are not serializing on.
Expand Down Expand Up @@ -61,6 +80,11 @@

private JmesPath<JsonNode> jmesPath = new JacksonRuntime(configuration, getObjectMapper());

// Static block to ensure CRaC registration happens at class loading time
static {
Core.getGlobalContext().register(get());
}

private JsonConfig() {
}

Expand Down Expand Up @@ -103,7 +127,46 @@
jmesPath = new JacksonRuntime(updatedConfig, getObjectMapper());
}

@Override
public void beforeCheckpoint(Context<? extends Resource> context) throws Exception {
// Preload classes first to ensure this always runs
ClassPreLoader.preloadClasses();

// Initialize key components
ObjectMapper mapper = getObjectMapper();
getJmesPath();

// Prime common AWS Lambda event types with realistic events
primeEventType(mapper, APIGatewayProxyRequestEvent.class,
"{\"httpMethod\":\"GET\",\"path\":\"/test\",\"headers\":{\"Content-Type\":\"application/json\"},\"requestContext\":{\"accountId\":\"123456789012\"}}");
primeEventType(mapper, APIGatewayV2HTTPEvent.class,
"{\"version\":\"2.0\",\"routeKey\":\"GET /test\",\"requestContext\":{\"http\":{\"method\":\"GET\"},\"accountId\":\"123456789012\"}}");
primeEventType(mapper, SQSEvent.class,
"{\"Records\":[{\"messageId\":\"test-id\",\"body\":\"test message\",\"eventSource\":\"aws:sqs\"}]}");
primeEventType(mapper, SNSEvent.class,
"{\"Records\":[{\"Sns\":{\"Message\":\"test message\",\"TopicArn\":\"arn:aws:sns:us-east-1:123456789012:test\"}}]}");
primeEventType(mapper, KinesisEvent.class,
"{\"Records\":[{\"kinesis\":{\"data\":\"dGVzdA==\",\"partitionKey\":\"test\"},\"eventSource\":\"aws:kinesis\"}]}");
primeEventType(mapper, ScheduledEvent.class,
"{\"source\":\"aws.events\",\"detail-type\":\"Scheduled Event\",\"detail\":{}}");

// Warm up JMESPath function registry
getJmesPath().compile("@").search(mapper.readTree("{\"test\":\"value\"}"));
}

@Override
public void afterRestore(Context<? extends Resource> context) throws Exception {
// No action needed after restore
}

private void primeEventType(ObjectMapper mapper, Class<?> eventClass, String sampleJson) throws Exception {
// Deserialize sample JSON to the event class
Object event = mapper.readValue(sampleJson, eventClass);
// Serialize back to JSON to warm up both directions
mapper.writeValueAsString(event);
}

private static class ConfigHolder {

Check failure on line 169 in powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/JsonConfig.java

View workflow job for this annotation

GitHub Actions / pmd_analyse

This class has only private constructors and may be final

Reports classes that may be made final because they cannot be extended from outside their compilation unit anyway. This is because all their constructors are private, so a subclass could not call the super constructor. ClassWithOnlyPrivateConstructorsShouldBeFinal (Priority: 1, Ruleset: Design) https://docs.pmd-code.org/snapshot/pmd_rules_java_design.html#classwithonlyprivateconstructorsshouldbefinal
private static final JsonConfig instance = new JsonConfig();
}
}
74 changes: 74 additions & 0 deletions powertools-serialization/src/main/resources/classesloaded.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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.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.databind.json.JsonMapper
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
com.fasterxml.jackson.annotation.JsonInclude
com.fasterxml.jackson.annotation.JsonInclude$Include
io.burt.jmespath.JmesPath
io.burt.jmespath.RuntimeConfiguration
io.burt.jmespath.RuntimeConfiguration$Builder
io.burt.jmespath.function.BaseFunction
io.burt.jmespath.function.FunctionRegistry
io.burt.jmespath.jackson.JacksonRuntime
software.amazon.lambda.powertools.utilities.JsonConfig
software.amazon.lambda.powertools.utilities.EventDeserializer
software.amazon.lambda.powertools.utilities.EventDeserializationException
software.amazon.lambda.powertools.utilities.jmespath.Base64Function
software.amazon.lambda.powertools.utilities.jmespath.Base64GZipFunction
software.amazon.lambda.powertools.utilities.jmespath.JsonFunction
com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent
com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent
com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent
com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse
com.amazonaws.services.lambda.runtime.events.ActiveMQEvent
com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent
com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerResponseEvent
com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent
com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent
com.amazonaws.services.lambda.runtime.events.KafkaEvent
com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsFirehoseInputPreprocessingEvent
com.amazonaws.services.lambda.runtime.events.KinesisAnalyticsStreamsInputPreprocessingEvent
com.amazonaws.services.lambda.runtime.events.KinesisEvent
com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent
com.amazonaws.services.lambda.runtime.events.RabbitMQEvent
com.amazonaws.services.lambda.runtime.events.SNSEvent
com.amazonaws.services.lambda.runtime.events.SQSEvent
com.amazonaws.services.lambda.runtime.events.ScheduledEvent
org.slf4j.Logger
org.slf4j.LoggerFactory
java.util.function.Supplier
java.lang.ThreadLocal
java.util.Map
java.util.HashMap
java.util.List
java.util.ArrayList
java.util.concurrent.ConcurrentHashMap
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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.utilities;

import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.mockito.Mockito.mock;

import org.crac.Context;
import org.crac.Resource;
import org.junit.jupiter.api.Test;

class JsonConfigCracTest {

JsonConfig config = JsonConfig.get();
Context<Resource> context = mock(Context.class);

@Test
void testBeforeCheckpointDoesNotThrowException() {
assertThatNoException().isThrownBy(() -> config.beforeCheckpoint(context));
}

@Test
void testAfterRestoreDoesNotThrowException() {
assertThatNoException().isThrownBy(() -> config.afterRestore(context));
}
Comment on lines +29 to +37
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After removing the exception wildcard catch these tests should still pass. Right now, they have no effect.

See comment: https://github.com/aws-powertools/powertools-lambda-java/pull/2104/files#r2314166695

}
Loading
Loading