Skip to content

Commit 212049c

Browse files
authored
Merge pull request #178 from graphql-java/load_intrumentation
Added a instrumentation of load calls
2 parents 8a068c2 + 325f82d commit 212049c

9 files changed

+146
-16
lines changed

src/main/java/org/dataloader/DataLoaderHelper.java

+7-3
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,16 @@ CompletableFuture<V> load(K key, Object loadContext) {
148148
boolean cachingEnabled = loaderOptions.cachingEnabled();
149149

150150
stats.incrementLoadCount(new IncrementLoadCountStatisticsContext<>(key, loadContext));
151-
151+
DataLoaderInstrumentationContext<Object> ctx = ctxOrNoopCtx(instrumentation().beginLoad(dataLoader, key,loadContext));
152+
CompletableFuture<V> cf;
152153
if (cachingEnabled) {
153-
return loadFromCache(key, loadContext, batchingEnabled);
154+
cf = loadFromCache(key, loadContext, batchingEnabled);
154155
} else {
155-
return queueOrInvokeLoader(key, loadContext, batchingEnabled, false);
156+
cf = queueOrInvokeLoader(key, loadContext, batchingEnabled, false);
156157
}
158+
ctx.onDispatched();
159+
cf.whenComplete(ctx::onCompleted);
160+
return cf;
157161
}
158162
}
159163

src/main/java/org/dataloader/instrumentation/ChainedDataLoaderInstrumentation.java

+6
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ public ChainedDataLoaderInstrumentation addAll(Collection<DataLoaderInstrumentat
6969
return new ChainedDataLoaderInstrumentation(list);
7070
}
7171

72+
73+
@Override
74+
public DataLoaderInstrumentationContext<Object> beginLoad(DataLoader<?, ?> dataLoader, Object key, Object loadContext) {
75+
return chainedCtx(it -> it.beginLoad(dataLoader, key, loadContext));
76+
}
77+
7278
@Override
7379
public DataLoaderInstrumentationContext<DispatchResult<?>> beginDispatch(DataLoader<?, ?> dataLoader) {
7480
return chainedCtx(it -> it.beginDispatch(dataLoader));

src/main/java/org/dataloader/instrumentation/DataLoaderInstrumentation.java

+15
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,21 @@
1212
*/
1313
@PublicSpi
1414
public interface DataLoaderInstrumentation {
15+
/**
16+
* This call back is done just before the {@link DataLoader#load(Object)} methods are invoked,
17+
* and it completes when the load promise is completed. If the value is a cached {@link java.util.concurrent.CompletableFuture}
18+
* then it might return almost immediately, otherwise it will return
19+
* when the batch load function is invoked and values get returned
20+
*
21+
* @param dataLoader the {@link DataLoader} in question
22+
* @param key the key used during the {@link DataLoader#load(Object)} call
23+
* @param loadContext the load context used during the {@link DataLoader#load(Object, Object)} call
24+
* @return a DataLoaderInstrumentationContext or null to be more performant
25+
*/
26+
default DataLoaderInstrumentationContext<Object> beginLoad(DataLoader<?, ?> dataLoader, Object key, Object loadContext) {
27+
return null;
28+
}
29+
1530
/**
1631
* This call back is done just before the {@link DataLoader#dispatch()} is invoked,
1732
* and it completes when the dispatch call promise is done.

src/test/java/org/dataloader/DataLoaderCacheMapTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public void should_access_to_future_dependants() {
4343
Collection<CompletableFuture<Integer>> futures = dataLoader.getCacheMap().getAll();
4444

4545
List<CompletableFuture<Integer>> futuresList = new ArrayList<>(futures);
46-
assertThat(futuresList.get(0).getNumberOfDependents(), equalTo(2));
47-
assertThat(futuresList.get(1).getNumberOfDependents(), equalTo(1));
46+
assertThat(futuresList.get(0).getNumberOfDependents(), equalTo(4)); // instrumentation is depending on the CF completing
47+
assertThat(futuresList.get(1).getNumberOfDependents(), equalTo(2));
4848
}
4949
}

src/test/java/org/dataloader/instrumentation/CapturingInstrumentation.java

+36-2
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,49 @@
66

77
import java.util.ArrayList;
88
import java.util.List;
9+
import java.util.stream.Collectors;
910

1011
class CapturingInstrumentation implements DataLoaderInstrumentation {
11-
String name;
12-
List<String> methods = new ArrayList<>();
12+
protected String name;
13+
protected List<String> methods = new ArrayList<>();
1314

1415
public CapturingInstrumentation(String name) {
1516
this.name = name;
1617
}
1718

19+
public String getName() {
20+
return name;
21+
}
22+
23+
public List<String> methods() {
24+
return methods;
25+
}
26+
27+
public List<String> notLoads() {
28+
return methods.stream().filter(method -> !method.contains("beginLoad")).collect(Collectors.toList());
29+
}
30+
31+
public List<String> onlyLoads() {
32+
return methods.stream().filter(method -> method.contains("beginLoad")).collect(Collectors.toList());
33+
}
34+
35+
36+
@Override
37+
public DataLoaderInstrumentationContext<Object> beginLoad(DataLoader<?, ?> dataLoader, Object key, Object loadContext) {
38+
methods.add(name + "_beginLoad" +"_k:" + key);
39+
return new DataLoaderInstrumentationContext<>() {
40+
@Override
41+
public void onDispatched() {
42+
methods.add(name + "_beginLoad_onDispatched"+"_k:" + key);
43+
}
44+
45+
@Override
46+
public void onCompleted(Object result, Throwable t) {
47+
methods.add(name + "_beginLoad_onCompleted"+"_k:" + key);
48+
}
49+
};
50+
}
51+
1852
@Override
1953
public DataLoaderInstrumentationContext<DispatchResult<?>> beginDispatch(DataLoader<?, ?> dataLoader) {
2054
methods.add(name + "_beginDispatch");

src/test/java/org/dataloader/instrumentation/CapturingInstrumentationReturnsNull.java

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ public CapturingInstrumentationReturnsNull(String name) {
1212
super(name);
1313
}
1414

15+
@Override
16+
public DataLoaderInstrumentationContext<Object> beginLoad(DataLoader<?, ?> dataLoader, Object key, Object loadContext) {
17+
methods.add(name + "_beginLoad" +"_k:" + key);
18+
return null;
19+
}
20+
1521
@Override
1622
public DataLoaderInstrumentationContext<DispatchResult<?>> beginDispatch(DataLoader<?, ?> dataLoader) {
1723
methods.add(name + "_beginDispatch");

src/test/java/org/dataloader/instrumentation/ChainedDataLoaderInstrumentationTest.java

+18-8
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,21 @@ void canChainTogetherOneInstrumentation() {
6161

6262
DataLoader<String, String> dl = DataLoaderFactory.newDataLoader(TestKit.keysAsValues(), options);
6363

64-
dl.load("A");
65-
dl.load("B");
64+
dl.load("X");
65+
dl.load("Y");
6666

6767
CompletableFuture<List<String>> dispatch = dl.dispatch();
6868

6969
await().until(dispatch::isDone);
7070

71-
assertThat(capturingA.methods, equalTo(List.of("A_beginDispatch",
71+
assertThat(capturingA.notLoads(), equalTo(List.of("A_beginDispatch",
7272
"A_beginBatchLoader", "A_beginBatchLoader_onDispatched", "A_beginBatchLoader_onCompleted",
7373
"A_beginDispatch_onDispatched", "A_beginDispatch_onCompleted")));
74+
75+
assertThat(capturingA.onlyLoads(), equalTo(List.of(
76+
"A_beginLoad_k:X", "A_beginLoad_onDispatched_k:X", "A_beginLoad_k:Y", "A_beginLoad_onDispatched_k:Y",
77+
"A_beginLoad_onCompleted_k:X", "A_beginLoad_onCompleted_k:Y"
78+
)));
7479
}
7580

7681

@@ -87,8 +92,8 @@ public void canChainTogetherManyInstrumentationsWithDifferentBatchLoaders(TestDa
8792

8893
DataLoader<String, String> dl = factory.idLoader(options);
8994

90-
dl.load("A");
91-
dl.load("B");
95+
dl.load("X");
96+
dl.load("Y");
9297

9398
CompletableFuture<List<String>> dispatch = dl.dispatch();
9499

@@ -98,16 +103,21 @@ public void canChainTogetherManyInstrumentationsWithDifferentBatchLoaders(TestDa
98103
// A_beginBatchLoader happens before A_beginDispatch_onDispatched because these are sync
99104
// and no async - a batch scheduler or async batch loader would change that
100105
//
101-
assertThat(capturingA.methods, equalTo(List.of("A_beginDispatch",
106+
assertThat(capturingA.notLoads(), equalTo(List.of("A_beginDispatch",
102107
"A_beginBatchLoader", "A_beginBatchLoader_onDispatched", "A_beginBatchLoader_onCompleted",
103108
"A_beginDispatch_onDispatched", "A_beginDispatch_onCompleted")));
104109

105-
assertThat(capturingB.methods, equalTo(List.of("B_beginDispatch",
110+
assertThat(capturingA.onlyLoads(), equalTo(List.of(
111+
"A_beginLoad_k:X", "A_beginLoad_onDispatched_k:X", "A_beginLoad_k:Y", "A_beginLoad_onDispatched_k:Y",
112+
"A_beginLoad_onCompleted_k:X", "A_beginLoad_onCompleted_k:Y"
113+
)));
114+
115+
assertThat(capturingB.notLoads(), equalTo(List.of("B_beginDispatch",
106116
"B_beginBatchLoader", "B_beginBatchLoader_onDispatched", "B_beginBatchLoader_onCompleted",
107117
"B_beginDispatch_onDispatched", "B_beginDispatch_onCompleted")));
108118

109119
// it returned null on all its contexts - nothing to call back on
110-
assertThat(capturingButReturnsNull.methods, equalTo(List.of("NULL_beginDispatch", "NULL_beginBatchLoader")));
120+
assertThat(capturingButReturnsNull.notLoads(), equalTo(List.of("NULL_beginDispatch", "NULL_beginBatchLoader")));
111121
}
112122

113123
@Test

src/test/java/org/dataloader/instrumentation/DataLoaderInstrumentationTest.java

+55
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,61 @@ public class DataLoaderInstrumentationTest {
2929
return keys;
3030
});
3131

32+
@Test
33+
void canMonitorLoading() {
34+
AtomicReference<DataLoader<?, ?>> dlRef = new AtomicReference<>();
35+
36+
CapturingInstrumentation instrumentation = new CapturingInstrumentation("x") {
37+
38+
@Override
39+
public DataLoaderInstrumentationContext<Object> beginLoad(DataLoader<?, ?> dataLoader, Object key, Object loadContext) {
40+
DataLoaderInstrumentationContext<Object> superCtx = super.beginLoad(dataLoader, key, loadContext);
41+
dlRef.set(dataLoader);
42+
return superCtx;
43+
}
44+
45+
@Override
46+
public DataLoaderInstrumentationContext<List<?>> beginBatchLoader(DataLoader<?, ?> dataLoader, List<?> keys, BatchLoaderEnvironment environment) {
47+
return DataLoaderInstrumentationHelper.noOpCtx();
48+
}
49+
};
50+
51+
DataLoaderOptions options = new DataLoaderOptions()
52+
.setInstrumentation(instrumentation)
53+
.setMaxBatchSize(5);
54+
55+
DataLoader<String, String> dl = DataLoaderFactory.newDataLoader(snoozingBatchLoader, options);
56+
57+
List<String> keys = new ArrayList<>();
58+
for (int i = 0; i < 3; i++) {
59+
String key = "X" + i;
60+
keys.add(key);
61+
dl.load(key);
62+
}
63+
64+
// load a key that is cached
65+
dl.load("X0");
66+
67+
CompletableFuture<List<String>> dispatch = dl.dispatch();
68+
69+
await().until(dispatch::isDone);
70+
assertThat(dlRef.get(), is(dl));
71+
assertThat(dispatch.join(), equalTo(keys));
72+
73+
// the batch loading means they start and are instrumentation dispatched before they all end up completing
74+
assertThat(instrumentation.onlyLoads(),
75+
equalTo(List.of(
76+
"x_beginLoad_k:X0", "x_beginLoad_onDispatched_k:X0",
77+
"x_beginLoad_k:X1", "x_beginLoad_onDispatched_k:X1",
78+
"x_beginLoad_k:X2", "x_beginLoad_onDispatched_k:X2",
79+
"x_beginLoad_k:X0", "x_beginLoad_onDispatched_k:X0", // second cached call counts
80+
"x_beginLoad_onCompleted_k:X0",
81+
"x_beginLoad_onCompleted_k:X0", // each load call counts
82+
"x_beginLoad_onCompleted_k:X1", "x_beginLoad_onCompleted_k:X2")));
83+
84+
}
85+
86+
3287
@Test
3388
void canMonitorDispatching() {
3489
Stopwatch stopwatch = Stopwatch.stopwatchUnStarted();

src/test/java/org/dataloader/instrumentation/DataLoaderRegistryInstrumentationTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ public void endToEndIntegrationTest(TestDataLoaderFactory factory) {
224224
await().until(loadA::isDone);
225225
assertThat(loadA.join(), equalTo("A"));
226226

227-
assertThat(instrA.methods, equalTo(List.of("A_beginDispatch",
227+
assertThat(instrA.notLoads(), equalTo(List.of("A_beginDispatch",
228228
"A_beginBatchLoader", "A_beginBatchLoader_onDispatched", "A_beginBatchLoader_onCompleted",
229229
"A_beginDispatch_onDispatched", "A_beginDispatch_onCompleted")));
230230
}

0 commit comments

Comments
 (0)