Skip to content

Commit 4ec5fe8

Browse files
authored
Merge pull request #175 from graphql-java/instrumentation-support
Instrumentation support for dataloader
2 parents cf79290 + 5379982 commit 4ec5fe8

20 files changed

+1254
-33
lines changed

README.md

+59
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,65 @@ When ticker mode is **true** the `ScheduledDataLoaderRegistry` algorithm is as f
750750
* If it returns **true**, then `dataLoader.dispatch()` is called **and** a task is scheduled to re-evaluate this specific dataloader in the near future
751751
* The re-evaluation tasks are run periodically according to the `registry.getScheduleDuration()`
752752

753+
## Instrumenting the data loader code
754+
755+
A `DataLoader` can have a `DataLoaderInstrumentation` associated with it. This callback interface is intended to provide
756+
insight into working of the `DataLoader` such as how long it takes to run or to allow for logging of key events.
757+
758+
You set the `DataLoaderInstrumentation` into the `DataLoaderOptions` at build time.
759+
760+
```java
761+
762+
763+
DataLoaderInstrumentation timingInstrumentation = new DataLoaderInstrumentation() {
764+
@Override
765+
public DataLoaderInstrumentationContext<DispatchResult<?>> beginDispatch(DataLoader<?, ?> dataLoader) {
766+
long then = System.currentTimeMillis();
767+
return DataLoaderInstrumentationHelper.whenCompleted((result, err) -> {
768+
long ms = System.currentTimeMillis() - then;
769+
System.out.println(format("dispatch time: %d ms", ms));
770+
});
771+
}
772+
773+
@Override
774+
public DataLoaderInstrumentationContext<List<?>> beginBatchLoader(DataLoader<?, ?> dataLoader, List<?> keys, BatchLoaderEnvironment environment) {
775+
long then = System.currentTimeMillis();
776+
return DataLoaderInstrumentationHelper.whenCompleted((result, err) -> {
777+
long ms = System.currentTimeMillis() - then;
778+
System.out.println(format("batch loader time: %d ms", ms));
779+
});
780+
}
781+
};
782+
DataLoaderOptions options = DataLoaderOptions.newOptions().setInstrumentation(timingInstrumentation);
783+
DataLoader<String, User> userDataLoader = DataLoaderFactory.newDataLoader(userBatchLoader, options);
784+
785+
```
786+
787+
The example shows how long the overall `DataLoader` dispatch takes or how long the batch loader takes to run.
788+
789+
### Instrumenting the DataLoaderRegistry
790+
791+
You can also associate a `DataLoaderInstrumentation` with a `DataLoaderRegistry`. Every `DataLoader` registered will be changed so that the registry
792+
`DataLoaderInstrumentation` is associated with it. This allows you to set just the one `DataLoaderInstrumentation` in place and it applies to all
793+
data loaders.
794+
795+
```java
796+
DataLoader<String, User> userDataLoader = DataLoaderFactory.newDataLoader(userBatchLoader);
797+
DataLoader<String, User> teamsDataLoader = DataLoaderFactory.newDataLoader(teamsBatchLoader);
798+
799+
DataLoaderRegistry registry = DataLoaderRegistry.newRegistry()
800+
.instrumentation(timingInstrumentation)
801+
.register("users", userDataLoader)
802+
.register("teams", teamsDataLoader)
803+
.build();
804+
805+
DataLoader<String, User> changedUsersDataLoader = registry.getDataLoader("users");
806+
```
807+
808+
The `timingInstrumentation` here will be associated with the `DataLoader` under the key `users` and the key `teams`. Note that since
809+
DataLoader is immutable, a new changed object is created so you must use the registry to get the `DataLoader`.
810+
811+
753812
## Other information sources
754813

755814
- [Facebook DataLoader Github repo](https://github.com/facebook/dataloader)

src/main/java/org/dataloader/DataLoaderFactory.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,7 @@ public Builder<K, V> options(DataLoaderOptions options) {
561561
return this;
562562
}
563563

564-
DataLoader<K, V> build() {
564+
public DataLoader<K, V> build() {
565565
return mkDataLoader(batchLoadFunction, options);
566566
}
567567
}

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

+32-6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import org.dataloader.annotations.GuardedBy;
44
import org.dataloader.annotations.Internal;
55
import org.dataloader.impl.CompletableFutureKit;
6+
import org.dataloader.instrumentation.DataLoaderInstrumentation;
7+
import org.dataloader.instrumentation.DataLoaderInstrumentationContext;
68
import org.dataloader.reactive.ReactiveSupport;
79
import org.dataloader.scheduler.BatchLoaderScheduler;
810
import org.dataloader.stats.StatisticsCollector;
@@ -34,6 +36,7 @@
3436
import static java.util.stream.Collectors.toList;
3537
import static org.dataloader.impl.Assertions.assertState;
3638
import static org.dataloader.impl.Assertions.nonNull;
39+
import static org.dataloader.instrumentation.DataLoaderInstrumentationHelper.ctxOrNoopCtx;
3740

3841
/**
3942
* This helps break up the large DataLoader class functionality, and it contains the logic to dispatch the
@@ -167,6 +170,8 @@ Object getCacheKeyWithContext(K key, Object context) {
167170
}
168171

169172
DispatchResult<V> dispatch() {
173+
DataLoaderInstrumentationContext<DispatchResult<?>> instrCtx = ctxOrNoopCtx(instrumentation().beginDispatch(dataLoader));
174+
170175
boolean batchingEnabled = loaderOptions.batchingEnabled();
171176
final List<K> keys;
172177
final List<Object> callContexts;
@@ -175,7 +180,8 @@ DispatchResult<V> dispatch() {
175180
int queueSize = loaderQueue.size();
176181
if (queueSize == 0) {
177182
lastDispatchTime.set(now());
178-
return emptyDispatchResult();
183+
instrCtx.onDispatched();
184+
return endDispatchCtx(instrCtx, emptyDispatchResult());
179185
}
180186

181187
// we copy the pre-loaded set of futures ready for dispatch
@@ -192,7 +198,8 @@ DispatchResult<V> dispatch() {
192198
lastDispatchTime.set(now());
193199
}
194200
if (!batchingEnabled) {
195-
return emptyDispatchResult();
201+
instrCtx.onDispatched();
202+
return endDispatchCtx(instrCtx, emptyDispatchResult());
196203
}
197204
final int totalEntriesHandled = keys.size();
198205
//
@@ -213,7 +220,15 @@ DispatchResult<V> dispatch() {
213220
} else {
214221
futureList = dispatchQueueBatch(keys, callContexts, queuedFutures);
215222
}
216-
return new DispatchResult<>(futureList, totalEntriesHandled);
223+
instrCtx.onDispatched();
224+
return endDispatchCtx(instrCtx, new DispatchResult<>(futureList, totalEntriesHandled));
225+
}
226+
227+
private DispatchResult<V> endDispatchCtx(DataLoaderInstrumentationContext<DispatchResult<?>> instrCtx, DispatchResult<V> dispatchResult) {
228+
// once the CF completes, we can tell the instrumentation
229+
dispatchResult.getPromisedResults()
230+
.whenComplete((result, throwable) -> instrCtx.onCompleted(dispatchResult, throwable));
231+
return dispatchResult;
217232
}
218233

219234
private CompletableFuture<List<V>> sliceIntoBatchesOfBatches(List<K> keys, List<CompletableFuture<V>> queuedFutures, List<Object> callContexts, int maxBatchSize) {
@@ -427,11 +442,14 @@ CompletableFuture<List<V>> invokeLoader(List<K> keys, List<Object> keyContexts,
427442
}
428443

429444
CompletableFuture<List<V>> invokeLoader(List<K> keys, List<Object> keyContexts, List<CompletableFuture<V>> queuedFutures) {
445+
Object context = loaderOptions.getBatchLoaderContextProvider().getContext();
446+
BatchLoaderEnvironment environment = BatchLoaderEnvironment.newBatchLoaderEnvironment()
447+
.context(context).keyContexts(keys, keyContexts).build();
448+
449+
DataLoaderInstrumentationContext<List<?>> instrCtx = ctxOrNoopCtx(instrumentation().beginBatchLoader(dataLoader, keys, environment));
450+
430451
CompletableFuture<List<V>> batchLoad;
431452
try {
432-
Object context = loaderOptions.getBatchLoaderContextProvider().getContext();
433-
BatchLoaderEnvironment environment = BatchLoaderEnvironment.newBatchLoaderEnvironment()
434-
.context(context).keyContexts(keys, keyContexts).build();
435453
if (isMapLoader()) {
436454
batchLoad = invokeMapBatchLoader(keys, environment);
437455
} else if (isPublisher()) {
@@ -441,12 +459,16 @@ CompletableFuture<List<V>> invokeLoader(List<K> keys, List<Object> keyContexts,
441459
} else {
442460
batchLoad = invokeListBatchLoader(keys, environment);
443461
}
462+
instrCtx.onDispatched();
444463
} catch (Exception e) {
464+
instrCtx.onDispatched();
445465
batchLoad = CompletableFutureKit.failedFuture(e);
446466
}
467+
batchLoad.whenComplete(instrCtx::onCompleted);
447468
return batchLoad;
448469
}
449470

471+
450472
@SuppressWarnings("unchecked")
451473
private CompletableFuture<List<V>> invokeListBatchLoader(List<K> keys, BatchLoaderEnvironment environment) {
452474
CompletionStage<List<V>> loadResult;
@@ -575,6 +597,10 @@ private boolean isMappedPublisher() {
575597
return batchLoadFunction instanceof MappedBatchPublisher;
576598
}
577599

600+
private DataLoaderInstrumentation instrumentation() {
601+
return loaderOptions.getInstrumentation();
602+
}
603+
578604
int dispatchDepth() {
579605
synchronized (dataLoader) {
580606
return loaderQueue.size();

src/main/java/org/dataloader/DataLoaderOptions.java

+42-12
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package org.dataloader;
1818

1919
import org.dataloader.annotations.PublicApi;
20+
import org.dataloader.instrumentation.DataLoaderInstrumentation;
21+
import org.dataloader.instrumentation.DataLoaderInstrumentationHelper;
2022
import org.dataloader.scheduler.BatchLoaderScheduler;
2123
import org.dataloader.stats.NoOpStatisticsCollector;
2224
import org.dataloader.stats.StatisticsCollector;
@@ -52,6 +54,7 @@ public class DataLoaderOptions {
5254
private final BatchLoaderContextProvider environmentProvider;
5355
private final ValueCacheOptions valueCacheOptions;
5456
private final BatchLoaderScheduler batchLoaderScheduler;
57+
private final DataLoaderInstrumentation instrumentation;
5558

5659
/**
5760
* Creates a new data loader options with default settings.
@@ -68,6 +71,7 @@ public DataLoaderOptions() {
6871
environmentProvider = NULL_PROVIDER;
6972
valueCacheOptions = DEFAULT_VALUE_CACHE_OPTIONS;
7073
batchLoaderScheduler = null;
74+
instrumentation = DataLoaderInstrumentationHelper.NOOP_INSTRUMENTATION;
7175
}
7276

7377
private DataLoaderOptions(Builder builder) {
@@ -82,6 +86,7 @@ private DataLoaderOptions(Builder builder) {
8286
this.environmentProvider = builder.environmentProvider;
8387
this.valueCacheOptions = builder.valueCacheOptions;
8488
this.batchLoaderScheduler = builder.batchLoaderScheduler;
89+
this.instrumentation = builder.instrumentation;
8590
}
8691

8792
/**
@@ -101,7 +106,8 @@ public DataLoaderOptions(DataLoaderOptions other) {
101106
this.statisticsCollector = other.statisticsCollector;
102107
this.environmentProvider = other.environmentProvider;
103108
this.valueCacheOptions = other.valueCacheOptions;
104-
batchLoaderScheduler = other.batchLoaderScheduler;
109+
this.batchLoaderScheduler = other.batchLoaderScheduler;
110+
this.instrumentation = other.instrumentation;
105111
}
106112

107113
/**
@@ -169,7 +175,7 @@ public boolean batchingEnabled() {
169175
* Sets the option that determines whether batch loading is enabled.
170176
*
171177
* @param batchingEnabled {@code true} to enable batch loading, {@code false} otherwise
172-
* @return the data loader options for fluent coding
178+
* @return a new data loader options instance for fluent coding
173179
*/
174180
public DataLoaderOptions setBatchingEnabled(boolean batchingEnabled) {
175181
return builder().setBatchingEnabled(batchingEnabled).build();
@@ -188,7 +194,7 @@ public boolean cachingEnabled() {
188194
* Sets the option that determines whether caching is enabled.
189195
*
190196
* @param cachingEnabled {@code true} to enable caching, {@code false} otherwise
191-
* @return the data loader options for fluent coding
197+
* @return a new data loader options instance for fluent coding
192198
*/
193199
public DataLoaderOptions setCachingEnabled(boolean cachingEnabled) {
194200
return builder().setCachingEnabled(cachingEnabled).build();
@@ -212,7 +218,7 @@ public boolean cachingExceptionsEnabled() {
212218
* Sets the option that determines whether exceptional values are cache enabled.
213219
*
214220
* @param cachingExceptionsEnabled {@code true} to enable caching exceptional values, {@code false} otherwise
215-
* @return the data loader options for fluent coding
221+
* @return a new data loader options instance for fluent coding
216222
*/
217223
public DataLoaderOptions setCachingExceptionsEnabled(boolean cachingExceptionsEnabled) {
218224
return builder().setCachingExceptionsEnabled(cachingExceptionsEnabled).build();
@@ -233,7 +239,7 @@ public Optional<CacheKey> cacheKeyFunction() {
233239
* Sets the function to use for creating the cache key, if caching is enabled.
234240
*
235241
* @param cacheKeyFunction the cache key function to use
236-
* @return the data loader options for fluent coding
242+
* @return a new data loader options instance for fluent coding
237243
*/
238244
public DataLoaderOptions setCacheKeyFunction(CacheKey<?> cacheKeyFunction) {
239245
return builder().setCacheKeyFunction(cacheKeyFunction).build();
@@ -254,7 +260,7 @@ public DataLoaderOptions setCacheKeyFunction(CacheKey<?> cacheKeyFunction) {
254260
* Sets the cache map implementation to use for caching, if caching is enabled.
255261
*
256262
* @param cacheMap the cache map instance
257-
* @return the data loader options for fluent coding
263+
* @return a new data loader options instance for fluent coding
258264
*/
259265
public DataLoaderOptions setCacheMap(CacheMap<?, ?> cacheMap) {
260266
return builder().setCacheMap(cacheMap).build();
@@ -275,7 +281,7 @@ public int maxBatchSize() {
275281
* before they are split into multiple class
276282
*
277283
* @param maxBatchSize the maximum batch size
278-
* @return the data loader options for fluent coding
284+
* @return a new data loader options instance for fluent coding
279285
*/
280286
public DataLoaderOptions setMaxBatchSize(int maxBatchSize) {
281287
return builder().setMaxBatchSize(maxBatchSize).build();
@@ -294,7 +300,7 @@ public StatisticsCollector getStatisticsCollector() {
294300
* a common value
295301
*
296302
* @param statisticsCollector the statistics collector to use
297-
* @return the data loader options for fluent coding
303+
* @return a new data loader options instance for fluent coding
298304
*/
299305
public DataLoaderOptions setStatisticsCollector(Supplier<StatisticsCollector> statisticsCollector) {
300306
return builder().setStatisticsCollector(nonNull(statisticsCollector)).build();
@@ -311,7 +317,7 @@ public BatchLoaderContextProvider getBatchLoaderContextProvider() {
311317
* Sets the batch loader environment provider that will be used to give context to batch load functions
312318
*
313319
* @param contextProvider the batch loader context provider
314-
* @return the data loader options for fluent coding
320+
* @return a new data loader options instance for fluent coding
315321
*/
316322
public DataLoaderOptions setBatchLoaderContextProvider(BatchLoaderContextProvider contextProvider) {
317323
return builder().setBatchLoaderContextProvider(nonNull(contextProvider)).build();
@@ -332,7 +338,7 @@ public DataLoaderOptions setBatchLoaderContextProvider(BatchLoaderContextProvide
332338
* Sets the value cache implementation to use for caching values, if caching is enabled.
333339
*
334340
* @param valueCache the value cache instance
335-
* @return the data loader options for fluent coding
341+
* @return a new data loader options instance for fluent coding
336342
*/
337343
public DataLoaderOptions setValueCache(ValueCache<?, ?> valueCache) {
338344
return builder().setValueCache(valueCache).build();
@@ -349,7 +355,7 @@ public ValueCacheOptions getValueCacheOptions() {
349355
* Sets the {@link ValueCacheOptions} that control how the {@link ValueCache} will be used
350356
*
351357
* @param valueCacheOptions the value cache options
352-
* @return the data loader options for fluent coding
358+
* @return a new data loader options instance for fluent coding
353359
*/
354360
public DataLoaderOptions setValueCacheOptions(ValueCacheOptions valueCacheOptions) {
355361
return builder().setValueCacheOptions(nonNull(valueCacheOptions)).build();
@@ -367,12 +373,29 @@ public BatchLoaderScheduler getBatchLoaderScheduler() {
367373
* to some future time.
368374
*
369375
* @param batchLoaderScheduler the scheduler
370-
* @return the data loader options for fluent coding
376+
* @return a new data loader options instance for fluent coding
371377
*/
372378
public DataLoaderOptions setBatchLoaderScheduler(BatchLoaderScheduler batchLoaderScheduler) {
373379
return builder().setBatchLoaderScheduler(batchLoaderScheduler).build();
374380
}
375381

382+
/**
383+
* @return the {@link DataLoaderInstrumentation} to use
384+
*/
385+
public DataLoaderInstrumentation getInstrumentation() {
386+
return instrumentation;
387+
}
388+
389+
/**
390+
* Sets in a new {@link DataLoaderInstrumentation}
391+
*
392+
* @param instrumentation the new {@link DataLoaderInstrumentation}
393+
* @return a new data loader options instance for fluent coding
394+
*/
395+
public DataLoaderOptions setInstrumentation(DataLoaderInstrumentation instrumentation) {
396+
return builder().setInstrumentation(instrumentation).build();
397+
}
398+
376399
private Builder builder() {
377400
return new Builder(this);
378401
}
@@ -389,6 +412,7 @@ public static class Builder {
389412
private BatchLoaderContextProvider environmentProvider;
390413
private ValueCacheOptions valueCacheOptions;
391414
private BatchLoaderScheduler batchLoaderScheduler;
415+
private DataLoaderInstrumentation instrumentation;
392416

393417
public Builder() {
394418
this(new DataLoaderOptions()); // use the defaults of the DataLoaderOptions for this builder
@@ -406,6 +430,7 @@ public Builder() {
406430
this.environmentProvider = other.environmentProvider;
407431
this.valueCacheOptions = other.valueCacheOptions;
408432
this.batchLoaderScheduler = other.batchLoaderScheduler;
433+
this.instrumentation = other.instrumentation;
409434
}
410435

411436
public Builder setBatchingEnabled(boolean batchingEnabled) {
@@ -463,6 +488,11 @@ public Builder setBatchLoaderScheduler(BatchLoaderScheduler batchLoaderScheduler
463488
return this;
464489
}
465490

491+
public Builder setInstrumentation(DataLoaderInstrumentation instrumentation) {
492+
this.instrumentation = nonNull(instrumentation);
493+
return this;
494+
}
495+
466496
public DataLoaderOptions build() {
467497
return new DataLoaderOptions(this);
468498
}

0 commit comments

Comments
 (0)