Add a caching API based on caffeine for use from instrumentation, not just javaagent (#2477)

* Add caching API

* Finish

* javadoc

* Extract dep

* git add

* Drift

* Spotbugs

* checkstyle

* Fix package

* Test Caffeine patch
This commit is contained in:
Anuraag Agrawal 2021-03-05 17:57:23 +09:00 committed by GitHub
parent 35e415c7d1
commit d7f8967ff6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 416 additions and 241 deletions

View File

@ -30,6 +30,9 @@ ext {
systemLambda : "1.1.0",
prometheus : "0.9.0",
assertj : '3.19.0',
awaitility : '4.0.3',
// Caffeine 2.x to support Java 8+. 3.x is 11+.
caffeine : '2.9.0',
testcontainers : '1.15.2'
]
@ -73,6 +76,7 @@ ext {
dependencies.create(group: 'io.prometheus', name: 'simpleclient', version: "${versions.prometheus}"),
dependencies.create(group: 'io.prometheus', name: 'simpleclient_httpserver', version: "${versions.prometheus}"),
],
caffeine : "com.github.ben-manes.caffeine:caffeine:${versions.caffeine}",
// Testing
@ -97,5 +101,6 @@ ext {
coroutines : dependencies.create(group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: "${versions.coroutines}"),
junitApi : "org.junit.jupiter:junit-jupiter-api:${versions.junit5}",
assertj : "org.assertj:assertj-core:${versions.assertj}",
awaitility : "org.awaitility:awaitility:${versions.awaitility}"
]
}

View File

@ -10,6 +10,7 @@
<Or>
<Class name="io.opentelemetry.instrumentation.test.utils.GcUtils"/>
<Class name="io.opentelemetry.javaagent.util.GcUtils"/>
<Class name="~io.opentelemetry.instrumentation.api.caching.CacheTest.*"/>
</Or>
<Bug pattern="DM_GC"/>
</Match>

View File

@ -0,0 +1,29 @@
plugins {
id "com.github.johnrengelman.shadow"
}
group = 'io.opentelemetry.instrumentation'
apply from: "$rootDir/gradle/java.gradle"
apply from: "$rootDir/gradle/publish.gradle"
dependencies {
implementation(deps.caffeine) {
exclude group: 'com.google.errorprone', module: 'error_prone_annotations'
exclude group: 'org.checkerframework', module: 'checker-qual'
}
}
shadowJar {
archiveClassifier.set("")
relocate "com.github.benmanes.caffeine", "io.opentelemetry.instrumentation.internal.shaded.caffeine"
minimize()
}
jar {
enabled = false
dependsOn shadowJar
}

View File

@ -0,0 +1,25 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package com.github.benmanes.caffeine.cache;
// Caffeine uses reflection to load cache implementations based on parameters specified by a user.
// We use gradle-shadow-plugin to minimize the dependency on Caffeine, but it does not allow
// specifying classes to keep, only artifacts. It's a relatively simple workaround for us to use
// this non-public class to create a static link to the required implementations we use.
final class CacheImplementations {
// Each type of cache has a cache implementation and a node implementation.
// Strong keys, strong values, maximum size
SSMS<?, ?> ssms; // cache
PSMS<?, ?> psms; // node
// Weak keys, strong values, maximum size
WSMS<?, ?> wsms; // cache
FSMS<?, ?> fsms; // node
private CacheImplementations() {}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.api.caching;
import java.util.function.Function;
/** A cache from keys to values. */
public interface Cache<K, V> {
/** Returns a new {@link CacheBuilder} to configure a {@link Cache}. */
static CacheBuilder newBuilder() {
return new CacheBuilder();
}
/**
* Returns the cached value associated with the provided {@code key}. If no value is cached yet,
* computes the value using {@code mappingFunction}, stores the result, and returns it.
*/
V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction);
}

View File

@ -0,0 +1,46 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.api.caching;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.Executor;
/** A builder of {@link Cache}. */
public final class CacheBuilder {
private final Caffeine<?, ?> caffeine = Caffeine.newBuilder();
/** Sets the maximum size of the cache. */
public CacheBuilder setMaximumSize(long maximumSize) {
caffeine.maximumSize(maximumSize);
return this;
}
/**
* Sets that keys should be referenced weakly. If used, keys will use identity comparison, not
* {@link Object#equals(Object)}.
*/
public CacheBuilder setWeakKeys() {
caffeine.weakKeys();
return this;
}
// Visible for testing
CacheBuilder setExecutor(Executor executor) {
caffeine.executor(executor);
return this;
}
/** Returns a new {@link Cache} with the settings of this {@link CacheBuilder}. */
public <K, V> Cache<K, V> build() {
@SuppressWarnings("unchecked")
com.github.benmanes.caffeine.cache.Cache<K, V> delegate =
(com.github.benmanes.caffeine.cache.Cache<K, V>) caffeine.build();
return new CaffeineCache<K, V>(delegate);
}
CacheBuilder() {}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.api.caching;
import java.util.Set;
import java.util.function.Function;
final class CaffeineCache<K, V> implements Cache<K, V> {
private final com.github.benmanes.caffeine.cache.Cache<K, V> delegate;
CaffeineCache(com.github.benmanes.caffeine.cache.Cache<K, V> delegate) {
this.delegate = delegate;
}
@Override
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
return delegate.get(key, mappingFunction);
}
// Visible for testing
Set<K> keySet() {
return delegate.asMap().keySet();
}
// Visible for testing
void cleanup() {
delegate.cleanUp();
}
}

View File

@ -0,0 +1 @@
Tests for this module are in the instrumentation-api project to verify against the shaded artifact.

View File

@ -4,6 +4,8 @@ apply from: "$rootDir/gradle/java.gradle"
apply from: "$rootDir/gradle/publish.gradle"
dependencies {
api project(":instrumentation-api-caching")
api deps.opentelemetryApi
api deps.opentelemetryContext
api deps.opentelemetrySemConv
@ -16,4 +18,5 @@ dependencies {
testImplementation project(':testing-common')
testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.6.0'
testImplementation deps.assertj
testImplementation deps.awaitility
}

View File

@ -0,0 +1,120 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.api.caching;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
class CacheTest {
@Nested
class StrongKeys {
@Test
void unbounded() {
Cache<String, String> cache = Cache.newBuilder().build();
CaffeineCache<?, ?> caffeineCache = ((CaffeineCache<?, ?>) cache);
assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow");
assertThat(caffeineCache.keySet()).hasSize(1);
assertThat(cache.computeIfAbsent("cat", unused -> "bark")).isEqualTo("meow");
assertThat(caffeineCache.keySet()).hasSize(1);
assertThat(cache.computeIfAbsent("dog", unused -> "bark")).isEqualTo("bark");
assertThat(caffeineCache.keySet()).hasSize(2);
assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow");
}
@Test
void bounded() {
Cache<String, String> cache = Cache.newBuilder().setMaximumSize(1).build();
CaffeineCache<?, ?> caffeineCache = ((CaffeineCache<?, ?>) cache);
assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow");
assertThat(caffeineCache.keySet()).hasSize(1);
assertThat(cache.computeIfAbsent("cat", unused -> "bark")).isEqualTo("meow");
assertThat(caffeineCache.keySet()).hasSize(1);
assertThat(cache.computeIfAbsent("dog", unused -> "bark")).isEqualTo("bark");
caffeineCache.cleanup();
assertThat(caffeineCache.keySet()).hasSize(1);
assertThat(cache.computeIfAbsent("cat", unused -> "purr")).isEqualTo("purr");
}
}
@Nested
class WeakKeys {
@Test
void unbounded() {
Cache<String, String> cache = Cache.newBuilder().setWeakKeys().build();
CaffeineCache<?, ?> caffeineCache = ((CaffeineCache<?, ?>) cache);
String cat = new String("cat");
String dog = new String("dog");
assertThat(cache.computeIfAbsent(cat, unused -> "meow")).isEqualTo("meow");
assertThat(caffeineCache.keySet()).hasSize(1);
assertThat(cache.computeIfAbsent(cat, unused -> "bark")).isEqualTo("meow");
assertThat(caffeineCache.keySet()).hasSize(1);
assertThat(cache.computeIfAbsent(dog, unused -> "bark")).isEqualTo("bark");
assertThat(caffeineCache.keySet()).hasSize(2);
assertThat(cache.computeIfAbsent(cat, unused -> "meow")).isEqualTo("meow");
cat = null;
System.gc();
// Wait for GC to be reflected.
await()
.untilAsserted(
() -> {
caffeineCache.cleanup();
assertThat(caffeineCache.keySet()).hasSize(1);
});
assertThat(cache.computeIfAbsent(dog, unused -> "bark")).isEqualTo("bark");
dog = null;
System.gc();
// Wait for GC to be reflected.
await()
.untilAsserted(
() -> {
caffeineCache.cleanup();
assertThat(caffeineCache.keySet()).isEmpty();
});
}
@Test
void bounded() throws Exception {
Cache<String, String> cache = Cache.newBuilder().setWeakKeys().setMaximumSize(1).build();
CaffeineCache<?, ?> caffeineCache = ((CaffeineCache<?, ?>) cache);
String cat = new String("cat");
String dog = new String("dog");
assertThat(cache.computeIfAbsent(cat, unused -> "meow")).isEqualTo("meow");
assertThat(caffeineCache.keySet()).hasSize(1);
assertThat(cache.computeIfAbsent(cat, unused -> "bark")).isEqualTo("meow");
assertThat(caffeineCache.keySet()).hasSize(1);
assertThat(cache.computeIfAbsent(dog, unused -> "bark")).isEqualTo("bark");
caffeineCache.cleanup();
assertThat(caffeineCache.keySet()).hasSize(1);
dog = null;
System.gc();
// Wait for GC to be reflected.
await()
.untilAsserted(
() -> {
caffeineCache.cleanup();
assertThat(caffeineCache.keySet()).isEmpty();
});
}
}
}

View File

@ -1,54 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.api;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
/** An LRU cache that has a fixed maximum size. */
public interface BoundedCache<K, V> {
V get(K key, Function<? super K, ? extends V> mappingFunction);
static <K, V> BoundedCache<K, V> build(long maxSize) {
return Provider.get().build(maxSize);
}
interface Builder {
<K, V> BoundedCache<K, V> build(long maxSize);
}
class Provider {
/*
The default implementation just delegates to the lookup function and should not normally be used.
It will be replaced at startup by the AgentInstaller.
*/
private static final Builder NEVER_ACTUALLY_CACHES =
new Builder() {
@Override
public <K, V> BoundedCache<K, V> build(long maxSize) {
return (key, mappingFunction) -> mappingFunction.apply(key);
}
};
private static final AtomicReference<Builder> builderRef =
new AtomicReference<>(NEVER_ACTUALLY_CACHES);
private Provider() {}
public static boolean registerIfAbsent(Builder builder) {
return builderRef.compareAndSet(NEVER_ACTUALLY_CACHES, builder);
}
// Method exists for testing only
static void reset() {
builderRef.set(NEVER_ACTUALLY_CACHES);
}
public static Builder get() {
return builderRef.get();
}
}
}

View File

@ -7,7 +7,7 @@ package io.opentelemetry.javaagent.instrumentation.api.db;
import static io.opentelemetry.javaagent.instrumentation.api.db.StatementSanitizationConfig.isStatementSanitizationEnabled;
import io.opentelemetry.javaagent.instrumentation.api.BoundedCache;
import io.opentelemetry.instrumentation.api.caching.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -18,14 +18,14 @@ import org.slf4j.LoggerFactory;
public final class SqlStatementSanitizer {
private static final Logger log = LoggerFactory.getLogger(SqlStatementSanitizer.class);
private static final BoundedCache<String, SqlStatementInfo> sqlToStatementInfoCache =
BoundedCache.build(1000);
private static final Cache<String, SqlStatementInfo> sqlToStatementInfoCache =
Cache.newBuilder().setMaximumSize(1000).build();
public static SqlStatementInfo sanitize(String statement) {
if (!isStatementSanitizationEnabled() || statement == null) {
return new SqlStatementInfo(statement, null, null);
}
return sqlToStatementInfoCache.get(
return sqlToStatementInfoCache.computeIfAbsent(
statement,
k -> {
log.trace("SQL statement cache miss");

View File

@ -1,104 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.api;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
class BoundedCacheTest {
private AtomicInteger cacheMisses;
@BeforeEach
void setup() {
cacheMisses = new AtomicInteger(0);
}
@AfterEach
void reset() {
BoundedCache.Provider.reset();
}
String mockLookupFunction(String s) {
cacheMisses.incrementAndGet();
return s.toUpperCase();
}
@Test
@Order(1)
void testCanUseBeforeRegister() {
BoundedCache<String, String> cache = BoundedCache.build(3);
String result1 = cache.get("foo", this::mockLookupFunction);
String result2 = cache.get("bAr", this::mockLookupFunction);
assertThat(result1).isEqualTo("FOO");
assertThat(result2).isEqualTo("BAR");
assertThat(cacheMisses.get()).isEqualTo(2);
}
@Test
@Order(2)
void testRegisterUsesInstance() {
Map<String, String> map = new HashMap<>();
BoundedCache.Builder builder = buildMapBackedBuilder(map);
BoundedCache.Provider.registerIfAbsent(builder);
BoundedCache<String, String> cache = BoundedCache.build(3);
String result1 = cache.get("foo", this::mockLookupFunction);
String result2 = cache.get("fOo", this::mockLookupFunction);
String result3 = cache.get("foo", this::mockLookupFunction);
assertThat(result1).isEqualTo("FOO");
assertThat(result2).isEqualTo("FOO");
assertThat(result3).isEqualTo("FOO");
assertThat(cacheMisses.get()).isEqualTo(2); // once for "foo" once for "fOo"
assertThat(map.size()).isEqualTo(2);
assertThat(map).containsKey("foo");
assertThat(map).containsKey("fOo");
}
@Test
@Order(3)
void testRegisterMultipleFails() {
Map<String, String> map = new HashMap<>();
BoundedCache.Builder builder = buildMapBackedBuilder(map);
assertThat(BoundedCache.Provider.registerIfAbsent(builder)).isTrue();
assertThat(BoundedCache.Provider.registerIfAbsent(builder)).isFalse();
}
@NotNull
private BoundedCache.Builder buildMapBackedBuilder(Map map) {
return new BoundedCache.Builder() {
@Override
public <K, V> BoundedCache<K, V> build(long maxSize) {
return new MapBackedCache<K, V>((Map<K, V>) map);
}
};
}
private static class MapBackedCache<K, V> implements BoundedCache<K, V> {
private final Map<K, V> map;
public MapBackedCache(Map<K, V> map) {
this.map = map;
}
@Override
public V get(K key, Function<? super K, ? extends V> mappingFunction) {
V v = map.get(key);
if (v == null) {
v = mappingFunction.apply(key);
map.put(key, v);
}
return v;
}
}
}

View File

@ -0,0 +1,10 @@
apply from: "$rootDir/gradle/java.gradle"
dependencies {
// For testing javaagent-bootstrap's Caffeine patch, we need to compile against our cache API
// but make sure to run against javaagent-bootstrap
testCompileOnly project(':instrumentation-api-caching')
testRuntimeOnly project(":javaagent-bootstrap")
testImplementation deps.assertj
}

View File

@ -0,0 +1,38 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.api.caching;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
class PatchCaffeineTest {
@Test
void cleanupNotForkJoinTask() {
AtomicReference<AssertionError> errorRef = new AtomicReference<>();
Cache<String, String> cache =
Cache.newBuilder()
.setExecutor(
task -> {
try {
assertThat(task).isNotInstanceOf(ForkJoinTask.class);
} catch (AssertionError e) {
errorRef.set(e);
}
})
.setMaximumSize(1)
.build();
assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow");
assertThat(cache.computeIfAbsent("dog", unused -> "bark")).isEqualTo("bark");
AssertionError error = errorRef.get();
if (error != null) {
throw error;
}
}
}

View File

@ -7,6 +7,18 @@ apply from: "$rootDir/gradle/publish.gradle"
minimumBranchCoverage = 0.0
minimumInstructionCoverage = 0.0
// patch inner class from Caffeine to avoid ForkJoinTask from being loaded too early
sourceSets {
patch {
java {}
}
}
jar {
from(sourceSets.patch.output) {
include 'io/opentelemetry/instrumentation/internal/shaded/caffeine/cache/BoundedLocalCache$PerformCleanupTask.class'
}
}
configurations {
// classpath used by the instrumentation muzzle plugin
instrumentationMuzzle {

View File

@ -0,0 +1,64 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
// Includes work from:
/*
* Copyright 2017 Datadog, Inc.
*
* 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.
*/
/*
* Copyright 2014 Ben Manes. All Rights Reserved.
*
* 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 io.opentelemetry.instrumentation.internal.shaded.caffeine.cache;
import java.lang.ref.WeakReference;
/** skeleton outer class just for compilation purposes, not included in the final patch. */
abstract class BoundedLocalCache<K, V> {
abstract void performCleanUp(Runnable task);
/** patched to not extend ForkJoinTask as we don't want that class loaded too early. */
static final class PerformCleanupTask implements Runnable {
private static final long serialVersionUID = 1L;
final WeakReference<BoundedLocalCache<?, ?>> reference;
PerformCleanupTask(BoundedLocalCache<?, ?> cache) {
reference = new WeakReference<BoundedLocalCache<?, ?>>(cache);
}
@Override
public void run() {
BoundedLocalCache<?, ?> cache = reference.get();
if (cache != null) {
cache.performCleanUp(/* ignored */ null);
}
}
}
}

View File

@ -74,8 +74,6 @@ public class AgentInstaller {
BootstrapPackagePrefixesHolder.setBoostrapPackagePrefixes(loadBootstrapPackagePrefixes());
// WeakMap is used by other classes below, so we need to register the provider first.
AgentTooling.registerWeakMapProvider();
// Instrumentation can use a bounded cache, so register here.
AgentTooling.registerBoundedCacheProvider();
// this needs to be done as early as possible - before the first Config.get() call
ConfigInitializer.initialize();
}

View File

@ -5,11 +5,8 @@
package io.opentelemetry.javaagent.tooling;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import io.opentelemetry.javaagent.bootstrap.WeakCache;
import io.opentelemetry.javaagent.bootstrap.WeakCache.Provider;
import io.opentelemetry.javaagent.instrumentation.api.BoundedCache;
import io.opentelemetry.javaagent.instrumentation.api.WeakMap;
import io.opentelemetry.javaagent.tooling.bytebuddy.AgentCachingPoolStrategy;
import io.opentelemetry.javaagent.tooling.bytebuddy.AgentLocationStrategy;
@ -36,21 +33,6 @@ public class AgentTooling {
}
}
/**
* Instances of BoundCache are backed by a guava instance that lives in the agent classloader and
* is bridged to user/instrumentation classloader through the BoundedCache.Provider interface.
*/
static void registerBoundedCacheProvider() {
BoundedCache.Provider.registerIfAbsent(
new BoundedCache.Builder() {
@Override
public <K, V> BoundedCache<K, V> build(long maxSize) {
Cache<K, V> cache = CacheBuilder.newBuilder().maximumSize(maxSize).build();
return new GuavaBoundedCache<>(cache);
}
});
}
private static <K, V> Provider loadWeakCacheProvider() {
Iterator<Provider> providers =
ServiceLoader.load(Provider.class, AgentInstaller.class.getClassLoader()).iterator();

View File

@ -1,29 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.tooling;
import com.google.common.cache.Cache;
import io.opentelemetry.javaagent.instrumentation.api.BoundedCache;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
class GuavaBoundedCache<K, V> implements BoundedCache<K, V> {
private final Cache<K, V> delegate;
public GuavaBoundedCache(Cache<K, V> delegate) {
this.delegate = delegate;
}
@Override
public V get(K key, Function<? super K, ? extends V> mappingFunction) {
try {
return delegate.get(key, () -> mappingFunction.apply(key));
} catch (ExecutionException e) {
throw new IllegalStateException("Unexpected cache exception", e);
}
}
}

View File

@ -1,30 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.tooling
import com.google.common.cache.CacheBuilder
import spock.lang.Specification
class GuavaBoundedCacheTest extends Specification {
def "test cache"() {
when:
def delegate = CacheBuilder.newBuilder().maximumSize(3).build()
def cache = new GuavaBoundedCache<>(delegate)
def fn = { x -> x.toUpperCase() }
then:
cache.get("foo", fn) == "FOO"
cache.get("bar", fn) == "BAR"
cache.get("baz", fn) == "BAZ"
cache.get("fizz", fn) == "FIZZ"
cache.get("buzz", fn) == "BUZZ"
delegate.size() == 3
}
}

View File

@ -35,6 +35,7 @@ rootProject.name = 'opentelemetry-java-instrumentation'
include ':opentelemetry-api-shaded-for-instrumenting'
include ':opentelemetry-ext-annotations-shaded-for-instrumenting'
include ':javaagent-bootstrap'
include ':javaagent-bootstrap-tests'
include ':javaagent-spi'
include ':javaagent-tooling'
include ':javaagent'
@ -42,6 +43,7 @@ include ':load-generator'
include ':bom-alpha'
include ':instrumentation-api'
include ':instrumentation-api-caching'
include ':javaagent-api'
// misc