-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Lettuce instrumentation support for versions 4.4.0.Final to latest
- Loading branch information
Showing
18 changed files
with
736 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
dependencies { | ||
implementation(project(":newrelic-security-api")) | ||
implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}") | ||
implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}") | ||
implementation 'biz.paluch.redis:lettuce:4.4.0.Final' | ||
} | ||
|
||
jar { | ||
manifest { | ||
attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.lettuce-4.3' | ||
} | ||
} | ||
|
||
verifyInstrumentation { | ||
passesOnly 'biz.paluch.redis:lettuce:[4.4.0.Final,4.5.0.Final]' | ||
excludeRegex '.*SNAPSHOT' | ||
} | ||
|
||
site { | ||
title 'Lettuce 4.3' | ||
type 'Framework' | ||
} |
96 changes: 96 additions & 0 deletions
96
...e-4.3/src/main/java/com/lambdaworks/redis/AbstractRedisAsyncCommands_Instrumentation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
/* | ||
* | ||
* * Copyright 2022 New Relic Corporation. All rights reserved. | ||
* * SPDX-License-Identifier: Apache-2.0 | ||
* | ||
*/ | ||
package com.lambdaworks.redis; | ||
|
||
import com.lambdaworks.redis.api.StatefulConnection; | ||
import com.lambdaworks.redis.protocol.*; | ||
import com.newrelic.agent.security.instrumentation.lettuce_4_3.LettuceUtils; | ||
import com.newrelic.api.agent.Trace; | ||
import com.newrelic.api.agent.security.NewRelicSecurity; | ||
import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; | ||
import com.newrelic.api.agent.security.schema.AbstractOperation; | ||
import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException; | ||
import com.newrelic.api.agent.security.schema.operation.RedisOperation; | ||
import com.newrelic.api.agent.weaver.Weave; | ||
import com.newrelic.api.agent.weaver.Weaver; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
@Weave(originalName = "com.lambdaworks.redis.AbstractRedisAsyncCommands") | ||
public abstract class AbstractRedisAsyncCommands_Instrumentation<K, V> { | ||
|
||
public abstract StatefulConnection<K, V> getConnection(); | ||
|
||
@SuppressWarnings("unchecked") | ||
@Trace | ||
public <T> AsyncCommand<K, V, T> dispatch(RedisCommand_Instrumentation<K, V, T> cmd) { | ||
boolean isLockAcquired = acquireLockIfPossible(); | ||
AbstractOperation operation = null; | ||
if(isLockAcquired) { | ||
operation = preprocessSecurityHook(cmd, LettuceUtils.METHOD_DISPATCH); | ||
} | ||
|
||
AsyncCommand<K, V, T> returnVal = null; | ||
try { | ||
returnVal = Weaver.callOriginal(); | ||
} finally { | ||
if(isLockAcquired){ | ||
releaseLock(); | ||
} | ||
} | ||
registerExitOperation(isLockAcquired, operation); | ||
|
||
return returnVal; | ||
} | ||
|
||
private void registerExitOperation(boolean isProcessingAllowed, com.newrelic.api.agent.security.schema.AbstractOperation operation) { | ||
try { | ||
if (operation == null || !isProcessingAllowed || !NewRelicSecurity.isHookProcessingActive() || | ||
NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty() || GenericHelper.skipExistsEvent() | ||
) { | ||
return; | ||
} | ||
NewRelicSecurity.getAgent().registerExitEvent(operation); | ||
} catch (Throwable ignored){} | ||
} | ||
|
||
private <T> AbstractOperation preprocessSecurityHook(RedisCommand_Instrumentation<K,V,T> cmd, String methodDispatch) { | ||
try { | ||
if (!NewRelicSecurity.isHookProcessingActive() || | ||
NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty()){ | ||
return null; | ||
} | ||
String type = cmd.getType().name(); | ||
CommandArgs_Instrumentation commandArgs = cmd.getArgs(); | ||
List<Object> arguments = new ArrayList<>(); | ||
for(int i=0 ; i<commandArgs.count(); i++){ | ||
Object arg = CommandArgsCsecUtils.getSingularArgs(commandArgs).get(i); | ||
arguments.add(CommandArgsCsecUtils.getArgument(arg)); | ||
} | ||
System.out.println(String.format("redis command : %s with arguments : %s", type, arguments)); | ||
} catch (Throwable e) { | ||
if (e instanceof NewRelicSecurityException) { | ||
throw e; | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
private void releaseLock() { | ||
try { | ||
GenericHelper.releaseLock(LettuceUtils.NR_SEC_CUSTOM_ATTRIB_NAME); | ||
} catch (Throwable ignored) {} | ||
} | ||
|
||
private boolean acquireLockIfPossible() { | ||
try { | ||
return GenericHelper.acquireLockIfPossible(LettuceUtils.NR_SEC_CUSTOM_ATTRIB_NAME); | ||
} catch (Throwable ignored) {} | ||
return false; | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
...ttuce-4.3/src/main/java/com/lambdaworks/redis/protocol/BytesArgument_Instrumentation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package com.lambdaworks.redis.protocol; | ||
|
||
import com.newrelic.api.agent.weaver.MatchType; | ||
import com.newrelic.api.agent.weaver.Weave; | ||
import com.newrelic.api.agent.weaver.Weaver; | ||
|
||
@Weave(type = MatchType.ExactClass, originalName = "com.lambdaworks.redis.protocol.CommandArgs$BytesArgument") | ||
abstract class BytesArgument_Instrumentation { | ||
private final byte[] val = Weaver.callOriginal(); | ||
} |
57 changes: 57 additions & 0 deletions
57
...curity/lettuce-4.3/src/main/java/com/lambdaworks/redis/protocol/CommandArgsCsecUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package com.lambdaworks.redis.protocol; | ||
|
||
import java.util.List; | ||
|
||
public class CommandArgsCsecUtils { | ||
|
||
public static List<CommandArgs.SingularArgument> getSingularArgs(CommandArgs_Instrumentation commandArgs) { | ||
return commandArgs.singularArguments; | ||
} | ||
|
||
public static byte[] getByteArgumentVal(CommandArgs.BytesArgument bytesArgument) { | ||
return bytesArgument.val; | ||
} | ||
|
||
public static long getIntegerArgument(CommandArgs.IntegerArgument integerArgument) { | ||
return integerArgument.val; | ||
} | ||
|
||
public static double getDoubleArgument(CommandArgs.DoubleArgument doubleArgument) { | ||
return doubleArgument.val; | ||
} | ||
|
||
public static String getStringArgument(CommandArgs.StringArgument stringArgument) { | ||
return stringArgument.val; | ||
} | ||
|
||
public static char[] getCharArrayArgument(CommandArgs.CharArrayArgument charArrayArgument) { | ||
return charArrayArgument.val; | ||
} | ||
|
||
public static Object getKeyArgument(CommandArgs.KeyArgument keyArgument) { | ||
return keyArgument.key; | ||
} | ||
|
||
public static Object getValueArgument(CommandArgs.ValueArgument valueArgument) { | ||
return valueArgument.val; | ||
} | ||
|
||
public static Object getArgument(Object arg) { | ||
if (arg instanceof CommandArgs.BytesArgument){ | ||
return getByteArgumentVal((CommandArgs.BytesArgument) arg); | ||
} else if (arg instanceof CommandArgs.IntegerArgument) { | ||
return getIntegerArgument((CommandArgs.IntegerArgument) arg); | ||
} else if (arg instanceof CommandArgs.DoubleArgument) { | ||
return getDoubleArgument((CommandArgs.DoubleArgument) arg); | ||
} else if (arg instanceof CommandArgs.StringArgument) { | ||
return getStringArgument((CommandArgs.StringArgument) arg); | ||
} else if (arg instanceof CommandArgs.CharArrayArgument) { | ||
return getCharArrayArgument((CommandArgs.CharArrayArgument) arg); | ||
} else if (arg instanceof CommandArgs.KeyArgument) { | ||
return getKeyArgument((CommandArgs.KeyArgument) arg); | ||
} else if (arg instanceof CommandArgs.ValueArgument) { | ||
return getValueArgument((CommandArgs.ValueArgument) arg); | ||
} | ||
return null; | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
...lettuce-4.3/src/main/java/com/lambdaworks/redis/protocol/CommandArgs_Instrumentation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.lambdaworks.redis.protocol; | ||
|
||
import com.newrelic.api.agent.weaver.NewField; | ||
import com.newrelic.api.agent.weaver.Weave; | ||
import com.newrelic.api.agent.weaver.Weaver; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
@Weave(originalName = "com.lambdaworks.redis.protocol.CommandArgs") | ||
public class CommandArgs_Instrumentation<K, V> { | ||
|
||
final List<CommandArgs.SingularArgument> singularArguments = Weaver.callOriginal(); | ||
|
||
// @NewField | ||
// public List<CommandArgs.SingularArgument> singularArguments_instrumentation; | ||
|
||
public int count() { | ||
return Weaver.callOriginal(); | ||
} | ||
|
||
} |
15 changes: 15 additions & 0 deletions
15
...ettuce-4.3/src/main/java/com/lambdaworks/redis/protocol/RedisCommand_Instrumentation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.lambdaworks.redis.protocol; | ||
|
||
import com.newrelic.api.agent.weaver.MatchType; | ||
import com.newrelic.api.agent.weaver.Weave; | ||
import com.newrelic.api.agent.weaver.Weaver; | ||
|
||
@Weave(type = MatchType.Interface, originalName = "com.lambdaworks.redis.protocol.RedisCommand") | ||
public abstract class RedisCommand_Instrumentation<K, V, T> { | ||
|
||
public abstract ProtocolKeyword getType(); | ||
public CommandArgs_Instrumentation<K, V> getArgs() { | ||
return Weaver.callOriginal(); | ||
} | ||
|
||
} |
13 changes: 13 additions & 0 deletions
13
...3/src/main/java/com/newrelic/agent/security/instrumentation/lettuce_4_3/LettuceUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.newrelic.agent.security.instrumentation.lettuce_4_3; | ||
|
||
public class LettuceUtils { | ||
|
||
public static final String NR_SEC_CUSTOM_ATTRIB_NAME = "REDIS_OPERATION_LOCK_LETTUCE-"; | ||
|
||
public static final String NR_SEC_CUSTOM_ATTR_FILTER_NAME = "REDIS_FILTER-"; | ||
public static final String METHOD_DISPATCH = "dispatch"; | ||
|
||
public static String getNrSecCustomAttribName(int hashCode) { | ||
return NR_SEC_CUSTOM_ATTR_FILTER_NAME + hashCode; | ||
} | ||
} |
126 changes: 126 additions & 0 deletions
126
...tuce-4.3/src/test/java/com/nr/lettuce43/instrumentation/Lettuce43InstrumentationTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
/* | ||
* | ||
* * Copyright 2022 New Relic Corporation. All rights reserved. | ||
* * SPDX-License-Identifier: Apache-2.0 | ||
* | ||
*/ | ||
package com.nr.lettuce43.instrumentation; | ||
|
||
import com.newrelic.agent.bridge.datastore.DatastoreVendor; | ||
import com.newrelic.agent.introspec.DatastoreHelper; | ||
import com.newrelic.agent.introspec.InstrumentationTestConfig; | ||
import com.newrelic.agent.introspec.InstrumentationTestRunner; | ||
import com.newrelic.agent.introspec.Introspector; | ||
import com.nr.lettuce43.instrumentation.helper.Data; | ||
import com.nr.lettuce43.instrumentation.helper.RedisDataService; | ||
import org.junit.Before; | ||
import org.junit.Rule; | ||
import org.junit.Test; | ||
import org.junit.runner.RunWith; | ||
import org.testcontainers.containers.GenericContainer; | ||
import org.testcontainers.utility.DockerImageName; | ||
|
||
import java.util.Collection; | ||
import java.util.concurrent.ExecutionException; | ||
|
||
import static org.junit.Assert.assertEquals; | ||
import static org.junit.Assert.assertTrue; | ||
|
||
@RunWith(InstrumentationTestRunner.class) | ||
@InstrumentationTestConfig(includePrefixes = {"com.lambdaworks.redis"}) | ||
public class Lettuce43InstrumentationTest { | ||
|
||
@Rule | ||
public GenericContainer redis = new GenericContainer(DockerImageName.parse("redis:5.0.3-alpine")) | ||
.withExposedPorts(6379); | ||
private RedisDataService redisDataService; | ||
|
||
@Before | ||
public void before() { | ||
redisDataService = new RedisDataService(redis); | ||
redisDataService.init(); | ||
} | ||
|
||
@Test | ||
public void testSync() { | ||
// given some data | ||
String key = "syncKey"; | ||
String value = "syncValue"; | ||
|
||
// when sync 'set' called | ||
String response = redisDataService.syncSet(key, value); | ||
|
||
// then response should be key | ||
assertEquals("Then response should be key", key, response); | ||
|
||
// when 'get' called | ||
String received = redisDataService.syncGet(key); | ||
|
||
// then value returned | ||
assertEquals("Get value", value, received); | ||
|
||
// and 2 transactions have been sent | ||
Introspector introspector = InstrumentationTestRunner.getIntrospector(); | ||
assertEquals("Finished transaction count", 2, introspector.getFinishedTransactionCount(1000)); | ||
Collection<String> transactionNames = introspector.getTransactionNames(); | ||
assertEquals("Transaction name count", 2, transactionNames.size()); | ||
|
||
String setTransactionName = "OtherTransaction/Custom/com.nr.lettuce43.instrumentation.helper.RedisDataService/syncSet"; | ||
String getTransactionName = "OtherTransaction/Custom/com.nr.lettuce43.instrumentation.helper.RedisDataService/syncGet"; | ||
|
||
// and transaction names are in collection | ||
assertTrue("Should contain transaction name for 'set'", transactionNames.contains(setTransactionName)); | ||
assertTrue("Should contain transaction name for 'get'", transactionNames.contains(getTransactionName)); | ||
|
||
// and required datastore metrics are sent | ||
DatastoreHelper helper = new DatastoreHelper(DatastoreVendor.Redis.name()); | ||
helper.assertAggregateMetrics(); | ||
|
||
assertEquals(1, introspector.getTransactionEvents(setTransactionName).iterator().next().getDatabaseCallCount()); | ||
assertEquals(1, introspector.getTransactionEvents(getTransactionName).iterator().next().getDatabaseCallCount()); | ||
helper.assertUnscopedOperationMetricCount("SET", 1); | ||
helper.assertUnscopedOperationMetricCount("GET", 1); | ||
|
||
helper.assertInstanceLevelMetric(DatastoreVendor.Redis.name(), redis.getHost(), redis.getFirstMappedPort().toString()); | ||
} | ||
|
||
@Test | ||
public void testAsync() throws ExecutionException, InterruptedException { | ||
// given some data | ||
Data data = new Data("asyncKey1", "asyncValue1"); | ||
|
||
// when async 'set' called | ||
String response = redisDataService.asyncSet(data); | ||
|
||
// then response should be 'OK' | ||
assertEquals("Then response should be 'OK'", "OK", response); | ||
|
||
// when async 'get' called | ||
String value = redisDataService.asyncGet(data.key); | ||
|
||
// then value returned | ||
assertEquals("Get value", data.value, value); | ||
|
||
// and 2 transactions have been sent | ||
Introspector introspector = InstrumentationTestRunner.getIntrospector(); | ||
assertEquals("Finished transaction count", 2, introspector.getFinishedTransactionCount(1000)); | ||
Collection<String> transactionNames = introspector.getTransactionNames(); | ||
assertEquals("Transaction name count", 2, transactionNames.size()); | ||
|
||
String setTransactionName = "OtherTransaction/Custom/com.nr.lettuce43.instrumentation.helper.RedisDataService/asyncSet"; | ||
String getTransactionName = "OtherTransaction/Custom/com.nr.lettuce43.instrumentation.helper.RedisDataService/asyncGet"; | ||
|
||
// and transaction names are in collection | ||
assertTrue("Should contain transaction name for 'set'", transactionNames.contains(setTransactionName)); | ||
assertTrue("Should contain transaction name for 'get'", transactionNames.contains(getTransactionName)); | ||
|
||
// and required datastore metrics are sent | ||
DatastoreHelper helper = new DatastoreHelper("Redis"); | ||
helper.assertAggregateMetrics(); | ||
assertEquals(1, introspector.getTransactionEvents(setTransactionName).iterator().next().getDatabaseCallCount()); | ||
assertEquals(1, introspector.getTransactionEvents(getTransactionName).iterator().next().getDatabaseCallCount()); | ||
helper.assertUnscopedOperationMetricCount("SET", 1); | ||
helper.assertUnscopedOperationMetricCount("GET", 1); | ||
helper.assertInstanceLevelMetric(DatastoreVendor.Redis.name(), redis.getHost(), redis.getFirstMappedPort().toString()); | ||
} | ||
} |
Oops, something went wrong.