Skip to content

Commit

Permalink
feat: merge record dynamicClass (#341)
Browse files Browse the repository at this point in the history
* feat: merge dynamic class record and replay

* feat: merge record and replay

* fix: sonar

* fix: sonar and UT

* feat: optimize name
  • Loading branch information
lucas-myx authored Jan 19, 2024
1 parent c5c1b1e commit 467bc43
Show file tree
Hide file tree
Showing 64 changed files with 2,438 additions and 186 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public static void initialize(Instrumentation inst, File agentFile, String agent
File[] extensionFiles = getExtensionJarFiles(agentFile);
classLoader = createAgentClassLoader(agentFile, extensionFiles);
InstrumentationHolder.setAgentClassLoader(classLoader);
InstrumentationHolder.setInstrumentation(inst);
AgentInstaller installer = createAgentInstaller(inst, agentFile, agentArgs);
addJarToLoaderSearch(agentFile, extensionFiles);
installer.install();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,22 @@
import java.lang.ref.WeakReference;
import java.util.concurrent.*;

class WeakCache<K, V> extends ReferenceQueue<K> implements Cache<K, V> {
public class WeakCache<K, V> extends ReferenceQueue<K> implements Cache<K, V> {
final ConcurrentMap<WeakReferenceKey<K>, V> target;

final CleanUpTask<V> cleanUpTask;

public WeakCache() {
this(new ConcurrentHashMap<>());
this(new ConcurrentHashMap<>(), null);
}

public WeakCache(CleanUpTask<V> cleanUpTask) {
this(new ConcurrentHashMap<>(), cleanUpTask);
}

public WeakCache(ConcurrentMap<WeakReferenceKey<K>, V> target) {
public WeakCache(ConcurrentMap<WeakReferenceKey<K>, V> target, CleanUpTask<V> cleanUpTask) {
this.target = target;
this.cleanUpTask = cleanUpTask;
}

public V get(K key) {
Expand All @@ -38,20 +45,31 @@ public void clear() {
target.clear();
}

public boolean containsKey(K key) {
check();
return target.containsKey(new WeakReferenceKey<>(key));
}

void check() {
Reference<?> reference;
while ((reference = poll()) != null) {
target.remove(reference);
final V value = target.remove(reference);
if (cleanUpTask != null && value != null) {
cleanUpTask.cleanUp(value);
}
}
}

static final class WeakReferenceKey<K> extends WeakReference<K> {
private final int hashCode;

WeakReferenceKey(K key) {
this(key, null);
}

WeakReferenceKey(K key, ReferenceQueue<? super K> queue) {
super(key, queue);

hashCode = System.identityHashCode(key);
hashCode = (key == null) ? 0 : System.identityHashCode(key);
}

@Override
Expand All @@ -68,5 +86,12 @@ public boolean equals(Object other) {
return other != null && other.equals(this);
}
}

public interface CleanUpTask<T> {
/**
* @param object object to cleanup
*/
void cleanUp(T object);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class ArexMocker implements Mocker {
private long creationTime;
private Mocker.Target targetRequest;
private Mocker.Target targetResponse;
private boolean needMerge;
private String operationName;

public ArexMocker() {
Expand Down Expand Up @@ -116,4 +117,12 @@ public void setTargetResponse(Mocker.Target targetResponse) {
public void setOperationName(String operationName) {
this.operationName = operationName;
}

public boolean isNeedMerge() {
return needMerge;
}

public void setNeedMerge(boolean needMerge) {
this.needMerge = needMerge;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public static MockCategoryType createDependency(String name) {

public static MockCategoryType create(String name, boolean entryPoint, boolean skipComparison) {
return CATEGORY_TYPE_MAP.computeIfAbsent(name,
key -> new MockCategoryType(name, entryPoint, skipComparison));
key -> new MockCategoryType(name, entryPoint, skipComparison));
}

public static Collection<MockCategoryType> values() {
Expand Down Expand Up @@ -78,6 +78,9 @@ private MockCategoryType(String name, boolean entryPoint, boolean skipComparison
this.skipComparison = skipComparison;
}

public static MockCategoryType of(String name) {
return CATEGORY_TYPE_MAP.get(name);
}

@Override
public String toString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,8 @@ default String recordLogTitle() {
default String replayLogTitle() {
return "replay." + getCategoryType().getName();
}

public boolean isNeedMerge();

public void setNeedMerge(boolean needMerge);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package io.arex.agent.bootstrap.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.*;

public class CollectionUtil {
private static final List<?> EMPTY_LIST = newArrayList();
Expand Down Expand Up @@ -41,4 +38,42 @@ public static <E> List<E> newArrayList(E... elements) {
Collections.addAll(list, elements);
return list;
}

/**
* split to multiple list by split count
*/
public static <V> List<List<V>> split(List<V> originalList, int splitCount) {
if (isEmpty(originalList)) {
return emptyList();
}
int originalSize = originalList.size();
List<List<V>> splitList = new ArrayList<>();
if (originalSize < splitCount || splitCount == 0) {
splitList.add(originalList);
return splitList;
}
for (int i = 0; i < splitCount; i++) {
List<V> list = new ArrayList<>();
splitList.add(list);
}
int index = 0;
for (V value : originalList) {
splitList.get(index).add(value);
index = (index + 1) % splitCount;
}
return splitList;
}

public static <V> List<V> filterNull(List<V> originalList) {
if (isEmpty(originalList)) {
return emptyList();
}
List<V> filterList = new ArrayList<>(originalList.size());
for (V element : originalList) {
if (element != null) {
filterList.add(element);
}
}
return filterList;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,26 @@ static int capacity(int expectedSize) {
// any large value
return Integer.MAX_VALUE;
}

public static <K> boolean getBoolean(final Map<? super K, ?> map, final K key) {
return getBoolean(map, key, false);
}

public static <K> boolean getBoolean(final Map<? super K, ?> map, final K key, boolean defaultValue) {
if (isEmpty(map) || map.get(key) == null) {
return defaultValue;
}
final Object answer = map.get(key);
if (answer instanceof Boolean) {
return (Boolean) answer;
}
if (answer instanceof String) {
return Boolean.parseBoolean((String) answer);
}
if (answer instanceof Number) {
final Number num = (Number) answer;
return num.intValue() != 0;
}
return defaultValue;
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package io.arex.agent.bootstrap.util;

import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.params.provider.Arguments.arguments;

import java.util.*;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

class CollectionUtilTest {

Expand Down Expand Up @@ -33,4 +39,33 @@ void newArrayList() {
actualResult = CollectionUtil.newArrayList("test");
assertInstanceOf(ArrayList.class, actualResult);
}

@ParameterizedTest
@MethodSource("splitCase")
void split(List<String> originalList, int splitCount, Predicate<List<List<String>>> predicate) {
assertTrue(predicate.test(CollectionUtil.split(originalList, splitCount)));
}

static Stream<Arguments> splitCase() {
Supplier<List<String>> lessSplitCountList = () -> CollectionUtil.newArrayList("mock");
Supplier<List<String>> normalSplitCountList = () -> CollectionUtil.newArrayList("mock1", "mock2");

Predicate<List<List<String>>> empty = CollectionUtil::isEmpty;
Predicate<List<List<String>>> notEmpty = CollectionUtil::isNotEmpty;

return Stream.of(
arguments(null, 1, empty),
arguments(lessSplitCountList.get(), 2, notEmpty),
arguments(normalSplitCountList.get(), 2, notEmpty)
);
}

@Test
void filterNull() {
List<String> actualResult = CollectionUtil.filterNull(null);
assertEquals(0, actualResult.size());

actualResult = CollectionUtil.filterNull(CollectionUtil.newArrayList("mock"));
assertEquals(1, actualResult.size());
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package io.arex.agent.bootstrap.util;

import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.params.provider.Arguments.arguments;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Stream;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

class MapUtilsTest {

Expand Down Expand Up @@ -41,4 +46,29 @@ public static int capacity(Map<String, String> map) {
return 0;
}
}

@ParameterizedTest
@MethodSource("getBooleanCase")
void getBoolean(Map<String, Object> map, String key, Predicate<Boolean> asserts) {
asserts.test(MapUtils.getBoolean(map, key));
}

static Stream<Arguments> getBooleanCase() {
Map<String, Object> map = new HashMap<>();
map.put("booleanKey", true);
map.put("stringKey", "true");
map.put("numberKey", 1);
map.put("key", String.class);

Predicate<Boolean> asserts1 = bool -> bool;
Predicate<Boolean> asserts2 = bool -> !bool;

return Stream.of(
arguments(null, null, asserts1),
arguments(map, "booleanKey", asserts2),
arguments(map, "stringKey", asserts2),
arguments(map, "numberKey", asserts2),
arguments(map, "key", asserts1)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
import io.arex.agent.bootstrap.util.ConcurrentHashSet;
import io.arex.agent.bootstrap.util.StringUtil;
import io.arex.inst.runtime.model.ArexConstants;
import io.arex.inst.runtime.model.MergeDTO;
import io.arex.inst.runtime.util.MergeRecordReplayUtil;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

public class ArexContext {
Expand All @@ -18,11 +18,13 @@ public class ArexContext {
private final long createTime;
private final AtomicInteger sequence;
private Set<Integer> methodSignatureHashList;
private Map<String, Object> cachedReplayResultMap;
private Map<Integer, List<MergeDTO>> cachedReplayResultMap;
private Map<String, Set<String>> excludeMockTemplate;

private Map<String, Object> attachments = null;

private LinkedBlockingQueue<MergeDTO> mergeRecordQueue;

private boolean isRedirectRequest;
private boolean isInvalidCase;

Expand Down Expand Up @@ -72,7 +74,7 @@ public Set<Integer> getMethodSignatureHashList() {
return methodSignatureHashList;
}

public Map<String, Object> getCachedReplayResultMap() {
public Map<Integer, List<MergeDTO>> getCachedReplayResultMap() {
if (cachedReplayResultMap == null) {
cachedReplayResultMap = new ConcurrentHashMap<>();
}
Expand Down Expand Up @@ -138,6 +140,13 @@ public boolean isRedirectRequest(String referer) {
return isRedirectRequest;
}

public LinkedBlockingQueue<MergeDTO> getMergeRecordQueue() {
if (mergeRecordQueue == null) {
mergeRecordQueue = new LinkedBlockingQueue<>(2048);
}
return mergeRecordQueue;
}

public void clear() {
if (methodSignatureHashList != null) {
methodSignatureHashList.clear();
Expand All @@ -151,5 +160,10 @@ public void clear() {
if (attachments != null) {
attachments.clear();
}
if (mergeRecordQueue != null) {
// async thread merge record (main entry has ended)
MergeRecordReplayUtil.recordRemain(this);
mergeRecordQueue.clear();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public static void registerListener(ContextListener listener) {

private static void publish(ArexContext context, boolean isCreate) {
if (CollectionUtil.isNotEmpty(LISTENERS)) {
LISTENERS.stream().forEach(listener -> {
LISTENERS.forEach(listener -> {
if (isCreate) {
listener.onCreate(context);
} else {
Expand Down
Loading

0 comments on commit 467bc43

Please sign in to comment.