Skip to content

Commit 0b9fba5

Browse files
committed
Instrumentation support for dataloader - Adde doco and SimpleDataLoaderInstrumentationContext
1 parent 292b283 commit 0b9fba5

File tree

5 files changed

+217
-0
lines changed

5 files changed

+217
-0
lines changed

README.md

Lines changed: 59 additions & 0 deletions
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/instrumentation/DataLoaderInstrumentationHelper.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import org.dataloader.annotations.PublicApi;
44

5+
import java.util.function.BiConsumer;
6+
57
@PublicApi
68
public class DataLoaderInstrumentationHelper {
79

@@ -33,6 +35,31 @@ public static <T> DataLoaderInstrumentationContext<T> noOpCtx() {
3335
public static final DataLoaderInstrumentation NOOP_INSTRUMENTATION = new DataLoaderInstrumentation() {
3436
};
3537

38+
/**
39+
* Allows for the more fluent away to return an instrumentation context that runs the specified
40+
* code on instrumentation step dispatch.
41+
*
42+
* @param codeToRun the code to run on dispatch
43+
* @param <U> the generic type
44+
* @return an instrumentation context
45+
*/
46+
public static <U> DataLoaderInstrumentationContext<U> whenDispatched(Runnable codeToRun) {
47+
return new SimpleDataLoaderInstrumentationContext<>(codeToRun, null);
48+
}
49+
50+
/**
51+
* Allows for the more fluent away to return an instrumentation context that runs the specified
52+
* code on instrumentation step completion.
53+
*
54+
* @param codeToRun the code to run on completion
55+
* @param <U> the generic type
56+
* @return an instrumentation context
57+
*/
58+
public static <U> DataLoaderInstrumentationContext<U> whenCompleted(BiConsumer<U, Throwable> codeToRun) {
59+
return new SimpleDataLoaderInstrumentationContext<>(null, codeToRun);
60+
}
61+
62+
3663
/**
3764
* Check the {@link DataLoaderInstrumentationContext} to see if its null and returns a noop if it is or else the original
3865
* context. This is a bit of a helper method.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.dataloader.instrumentation;
2+
3+
4+
import org.dataloader.annotations.Internal;
5+
6+
import java.util.function.BiConsumer;
7+
8+
/**
9+
* A simple implementation of {@link DataLoaderInstrumentationContext}
10+
*/
11+
@Internal
12+
class SimpleDataLoaderInstrumentationContext<T> implements DataLoaderInstrumentationContext<T> {
13+
14+
private final BiConsumer<T, Throwable> codeToRunOnComplete;
15+
private final Runnable codeToRunOnDispatch;
16+
17+
SimpleDataLoaderInstrumentationContext(Runnable codeToRunOnDispatch, BiConsumer<T, Throwable> codeToRunOnComplete) {
18+
this.codeToRunOnComplete = codeToRunOnComplete;
19+
this.codeToRunOnDispatch = codeToRunOnDispatch;
20+
}
21+
22+
@Override
23+
public void onDispatched() {
24+
if (codeToRunOnDispatch != null) {
25+
codeToRunOnDispatch.run();
26+
}
27+
}
28+
29+
@Override
30+
public void onCompleted(T result, Throwable t) {
31+
if (codeToRunOnComplete != null) {
32+
codeToRunOnComplete.accept(result, t);
33+
}
34+
}
35+
}

src/test/java/ReadmeExamples.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,17 @@
66
import org.dataloader.DataLoader;
77
import org.dataloader.DataLoaderFactory;
88
import org.dataloader.DataLoaderOptions;
9+
import org.dataloader.DataLoaderRegistry;
10+
import org.dataloader.DispatchResult;
911
import org.dataloader.MappedBatchLoaderWithContext;
1012
import org.dataloader.MappedBatchPublisher;
1113
import org.dataloader.Try;
1214
import org.dataloader.fixtures.SecurityCtx;
1315
import org.dataloader.fixtures.User;
1416
import org.dataloader.fixtures.UserManager;
17+
import org.dataloader.instrumentation.DataLoaderInstrumentation;
18+
import org.dataloader.instrumentation.DataLoaderInstrumentationContext;
19+
import org.dataloader.instrumentation.DataLoaderInstrumentationHelper;
1520
import org.dataloader.registries.DispatchPredicate;
1621
import org.dataloader.registries.ScheduledDataLoaderRegistry;
1722
import org.dataloader.scheduler.BatchLoaderScheduler;
@@ -228,6 +233,7 @@ private void clearCacheOnError() {
228233
}
229234

230235
BatchLoader<String, User> userBatchLoader;
236+
BatchLoader<String, User> teamsBatchLoader;
231237

232238
private void disableCache() {
233239
DataLoaderFactory.newDataLoader(userBatchLoader, DataLoaderOptions.newOptions().setCachingEnabled(false));
@@ -380,4 +386,45 @@ private void ScheduledDispatcherChained() {
380386
.build();
381387

382388
}
389+
390+
private DataLoaderInstrumentation timingInstrumentation = DataLoaderInstrumentationHelper.NOOP_INSTRUMENTATION;
391+
392+
private void instrumentationExample() {
393+
394+
DataLoaderInstrumentation timingInstrumentation = new DataLoaderInstrumentation() {
395+
@Override
396+
public DataLoaderInstrumentationContext<DispatchResult<?>> beginDispatch(DataLoader<?, ?> dataLoader) {
397+
long then = System.currentTimeMillis();
398+
return DataLoaderInstrumentationHelper.whenCompleted((result, err) -> {
399+
long ms = System.currentTimeMillis() - then;
400+
System.out.println(format("dispatch time: %d ms", ms));
401+
});
402+
}
403+
404+
@Override
405+
public DataLoaderInstrumentationContext<List<?>> beginBatchLoader(DataLoader<?, ?> dataLoader, List<?> keys, BatchLoaderEnvironment environment) {
406+
long then = System.currentTimeMillis();
407+
return DataLoaderInstrumentationHelper.whenCompleted((result, err) -> {
408+
long ms = System.currentTimeMillis() - then;
409+
System.out.println(format("batch loader time: %d ms", ms));
410+
});
411+
}
412+
};
413+
DataLoaderOptions options = DataLoaderOptions.newOptions().setInstrumentation(timingInstrumentation);
414+
DataLoader<String, User> userDataLoader = DataLoaderFactory.newDataLoader(userBatchLoader, options);
415+
}
416+
417+
private void registryExample() {
418+
DataLoader<String, User> userDataLoader = DataLoaderFactory.newDataLoader(userBatchLoader);
419+
DataLoader<String, User> teamsDataLoader = DataLoaderFactory.newDataLoader(teamsBatchLoader);
420+
421+
DataLoaderRegistry registry = DataLoaderRegistry.newRegistry()
422+
.instrumentation(timingInstrumentation)
423+
.register("users", userDataLoader)
424+
.register("teams", teamsDataLoader)
425+
.build();
426+
427+
DataLoader<String, User> changedUsersDataLoader = registry.getDataLoader("users");
428+
429+
}
383430
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.dataloader.instrumentation;
2+
3+
import org.hamcrest.Matchers;
4+
import org.junit.jupiter.api.Test;
5+
6+
import java.util.concurrent.atomic.AtomicBoolean;
7+
import java.util.concurrent.atomic.AtomicReference;
8+
9+
import static org.hamcrest.MatcherAssert.assertThat;
10+
import static org.hamcrest.Matchers.nullValue;
11+
12+
public class SimpleDataLoaderInstrumentationContextTest {
13+
14+
@Test
15+
void canRunCompletedCodeAsExpected() {
16+
AtomicReference<Object> actual = new AtomicReference<>();
17+
AtomicReference<Object> actualErr = new AtomicReference<>();
18+
19+
DataLoaderInstrumentationContext<Object> ctx = DataLoaderInstrumentationHelper.whenCompleted((r, err) -> {
20+
actualErr.set(err);
21+
actual.set(r);
22+
});
23+
24+
ctx.onDispatched(); // nothing happens
25+
assertThat(actual.get(), nullValue());
26+
assertThat(actualErr.get(), nullValue());
27+
28+
ctx.onCompleted("X", null);
29+
assertThat(actual.get(), Matchers.equalTo("X"));
30+
assertThat(actualErr.get(), nullValue());
31+
32+
ctx.onCompleted(null, new RuntimeException());
33+
assertThat(actual.get(), nullValue());
34+
assertThat(actualErr.get(), Matchers.instanceOf(RuntimeException.class));
35+
}
36+
37+
@Test
38+
void canRunOnDispatchCodeAsExpected() {
39+
AtomicBoolean dispatchedCalled = new AtomicBoolean();
40+
41+
DataLoaderInstrumentationContext<Object> ctx = DataLoaderInstrumentationHelper.whenDispatched(() -> dispatchedCalled.set(true));
42+
43+
ctx.onCompleted("X", null); // nothing happens
44+
assertThat(dispatchedCalled.get(), Matchers.equalTo(false));
45+
46+
ctx.onDispatched();
47+
assertThat(dispatchedCalled.get(), Matchers.equalTo(true));
48+
}
49+
}

0 commit comments

Comments
 (0)