Skip to content
This repository was archived by the owner on May 30, 2024. It is now read-only.

Commit fc3d40e

Browse files
committed
Merge branch '5.x' into eb/ch77594/coverage-4-misc
2 parents 2d0628d + 6ddb11b commit fc3d40e

File tree

9 files changed

+557
-2
lines changed

9 files changed

+557
-2
lines changed

.circleci/config.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ workflows:
3131
- packaging:
3232
requires:
3333
- build-linux
34+
- benchmarks:
35+
requires:
36+
- build-linux
3437
- build-test-windows:
3538
name: Java 11 - Windows - OpenJDK
3639

@@ -138,3 +141,19 @@ jobs:
138141
- run:
139142
name: run packaging tests
140143
command: cd packaging-test && make all
144+
145+
benchmarks:
146+
docker:
147+
- image: circleci/openjdk:11
148+
steps:
149+
- run: java -version
150+
- run: sudo apt-get install make -y -q
151+
- checkout
152+
- attach_workspace:
153+
at: build
154+
- run: cat gradle.properties.example >>gradle.properties
155+
- run:
156+
name: run benchmarks
157+
command: cd benchmarks && make
158+
- store_artifacts:
159+
path: benchmarks/build/reports/jmh

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ out/
1717
classes/
1818

1919
packaging-test/temp/
20+
benchmarks/lib/

CONTRIBUTING.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
Contributing to the LaunchDarkly Server-side SDK for Java
2-
================================================
1+
# Contributing to the LaunchDarkly Server-side SDK for Java
32

43
LaunchDarkly has published an [SDK contributor's guide](https://docs.launchdarkly.com/docs/sdk-contributors-guide) that provides a detailed explanation of how our SDKs work. See below for additional information on how to contribute to this SDK.
54

@@ -43,6 +42,10 @@ To build the SDK and run all unit tests:
4342
./gradlew test
4443
```
4544

45+
### Benchmarks
46+
47+
The project in the `benchmarks` subdirectory uses [JMH](https://openjdk.java.net/projects/code-tools/jmh/) to generate performance metrics for the SDK. This is run as a CI job, and can also be run manually by running `make` within `benchmarks` and then inspecting `build/reports/jmh`.
48+
4649
## Code coverage
4750

4851
It is important to keep unit test coverage as close to 100% as possible in this project. You can view the latest code coverage report in CircleCI, as `coverage/html/index.html` in the artifacts for the "Java 11 - Linux - OpenJDK" job. You can also run the report locally with `./gradlew jacocoTestCoverage` and view `./build/reports/jacoco/test`.

benchmarks/Makefile

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
.PHONY: benchmark clean sdk
2+
3+
BASE_DIR:=$(shell pwd)
4+
PROJECT_DIR=$(shell cd .. && pwd)
5+
SDK_VERSION=$(shell grep "version=" $(PROJECT_DIR)/gradle.properties | cut -d '=' -f 2)
6+
7+
BENCHMARK_ALL_JAR=lib/launchdarkly-java-server-sdk-all.jar
8+
BENCHMARK_TEST_JAR=lib/launchdarkly-java-server-sdk-test.jar
9+
SDK_JARS_DIR=$(PROJECT_DIR)/build/libs
10+
SDK_ALL_JAR=$(SDK_JARS_DIR)/launchdarkly-java-server-sdk-$(SDK_VERSION)-all.jar
11+
SDK_TEST_JAR=$(SDK_JARS_DIR)/launchdarkly-java-server-sdk-$(SDK_VERSION)-test.jar
12+
13+
benchmark: $(BENCHMARK_ALL_JAR) $(BENCHMARK_TEST_JAR)
14+
rm -rf build/tmp
15+
../gradlew jmh
16+
cat build/reports/jmh/human.txt
17+
../gradlew jmhReport
18+
19+
clean:
20+
rm -rf build lib
21+
22+
sdk: $(BENCHMARK_ALL_JAR) $(BENCHMARK_TEST_JAR)
23+
24+
$(BENCHMARK_ALL_JAR): $(SDK_ALL_JAR)
25+
mkdir -p lib
26+
cp $< $@
27+
28+
$(BENCHMARK_TEST_JAR): $(SDK_TEST_JAR)
29+
mkdir -p lib
30+
cp $< $@
31+
32+
$(SDK_ALL_JAR):
33+
cd .. && ./gradlew shadowJarAll
34+
35+
$(SDK_TEST_JAR):
36+
cd .. && ./gradlew testJar

benchmarks/build.gradle

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
2+
buildscript {
3+
repositories {
4+
jcenter()
5+
mavenCentral()
6+
}
7+
}
8+
9+
plugins {
10+
id "me.champeau.gradle.jmh" version "0.5.0"
11+
id "io.morethan.jmhreport" version "0.9.0"
12+
}
13+
14+
repositories {
15+
mavenCentral()
16+
}
17+
18+
ext.versions = [
19+
"jmh": "1.21",
20+
"guava": "19.0"
21+
]
22+
23+
dependencies {
24+
compile files("lib/launchdarkly-java-server-sdk-all.jar")
25+
compile files("lib/launchdarkly-java-server-sdk-test.jar")
26+
compile "com.google.code.gson:gson:2.7"
27+
compile "com.google.guava:guava:${versions.guava}" // required by SDK test code
28+
compile "com.squareup.okhttp3:mockwebserver:3.12.10"
29+
compile "org.openjdk.jmh:jmh-core:1.21"
30+
compile "org.openjdk.jmh:jmh-generator-annprocess:${versions.jmh}"
31+
}
32+
33+
jmh {
34+
iterations = 10 // Number of measurement iterations to do.
35+
benchmarkMode = ['avgt'] // "average time" - reports execution time as ns/op and allocations as B/op.
36+
// batchSize = 1 // Batch size: number of benchmark method calls per operation. (some benchmark modes can ignore this setting)
37+
fork = 1 // How many times to forks a single benchmark. Use 0 to disable forking altogether
38+
// failOnError = false // Should JMH fail immediately if any benchmark had experienced the unrecoverable error?
39+
forceGC = true // Should JMH force GC between iterations?
40+
humanOutputFile = project.file("${project.buildDir}/reports/jmh/human.txt") // human-readable output file
41+
// resultsFile = project.file("${project.buildDir}/reports/jmh/results.txt") // results file
42+
operationsPerInvocation = 3 // Operations per invocation.
43+
// benchmarkParameters = [:] // Benchmark parameters.
44+
profilers = [ 'gc' ] // Use profilers to collect additional data. Supported profilers: [cl, comp, gc, stack, perf, perfnorm, perfasm, xperf, xperfasm, hs_cl, hs_comp, hs_gc, hs_rt, hs_thr]
45+
timeOnIteration = '1s' // Time to spend at each measurement iteration.
46+
resultFormat = 'JSON' // Result format type (one of CSV, JSON, NONE, SCSV, TEXT)
47+
// synchronizeIterations = false // Synchronize iterations?
48+
// threads = 4 // Number of worker threads to run with.
49+
// timeout = '1s' // Timeout for benchmark iteration.
50+
timeUnit = 'ns' // Output time unit. Available time units are: [m, s, ms, us, ns].
51+
verbosity = 'NORMAL' // Verbosity mode. Available modes are: [SILENT, NORMAL, EXTRA]
52+
warmup = '1s' // Time to spend at each warmup iteration.
53+
warmupBatchSize = 2 // Warmup batch size: number of benchmark method calls per operation.
54+
warmupIterations = 1 // Number of warmup iterations to do.
55+
// warmupForks = 0 // How many warmup forks to make for a single benchmark. 0 to disable warmup forks.
56+
// warmupMode = 'INDI' // Warmup mode for warming up selected benchmarks. Warmup modes are: [INDI, BULK, BULK_INDI].
57+
58+
jmhVersion = versions.jmh
59+
}
60+
61+
jmhReport {
62+
jmhResultPath = project.file('build/reports/jmh/results.json')
63+
jmhReportOutput = project.file('build/reports/jmh')
64+
}

benchmarks/settings.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rootProject.name = 'launchdarkly-java-server-sdk-benchmarks'
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package com.launchdarkly.sdk.server;
2+
3+
import com.launchdarkly.sdk.LDValue;
4+
import com.launchdarkly.sdk.server.interfaces.Event;
5+
import com.launchdarkly.sdk.server.interfaces.EventProcessor;
6+
import com.launchdarkly.sdk.server.interfaces.EventSender;
7+
import com.launchdarkly.sdk.server.interfaces.EventSenderFactory;
8+
import com.launchdarkly.sdk.server.interfaces.HttpConfiguration;
9+
10+
import org.openjdk.jmh.annotations.Benchmark;
11+
import org.openjdk.jmh.annotations.Scope;
12+
import org.openjdk.jmh.annotations.State;
13+
14+
import java.io.IOException;
15+
import java.net.URI;
16+
import java.util.ArrayList;
17+
import java.util.List;
18+
import java.util.Random;
19+
import java.util.concurrent.CountDownLatch;
20+
21+
import static com.launchdarkly.sdk.server.TestValues.BASIC_USER;
22+
import static com.launchdarkly.sdk.server.TestValues.CUSTOM_EVENT;
23+
import static com.launchdarkly.sdk.server.TestValues.TEST_EVENTS_COUNT;
24+
25+
public class EventProcessorBenchmarks {
26+
private static final int EVENT_BUFFER_SIZE = 1000;
27+
private static final int FLAG_COUNT = 10;
28+
private static final int FLAG_VERSIONS = 3;
29+
private static final int FLAG_VARIATIONS = 2;
30+
31+
@State(Scope.Thread)
32+
public static class BenchmarkInputs {
33+
// Initialization of the things in BenchmarkInputs does not count as part of a benchmark.
34+
final EventProcessor eventProcessor;
35+
final MockEventSender eventSender;
36+
final List<Event.FeatureRequest> featureRequestEventsWithoutTracking = new ArrayList<>();
37+
final List<Event.FeatureRequest> featureRequestEventsWithTracking = new ArrayList<>();
38+
final Random random;
39+
40+
public BenchmarkInputs() {
41+
// MockEventSender does no I/O - it discards every event payload. So we are benchmarking
42+
// all of the event processing steps up to that point, including the formatting of the
43+
// JSON data in the payload.
44+
eventSender = new MockEventSender();
45+
46+
eventProcessor = Components.sendEvents()
47+
.capacity(EVENT_BUFFER_SIZE)
48+
.eventSender(new MockEventSenderFactory(eventSender))
49+
.createEventProcessor(TestComponents.clientContext(TestValues.SDK_KEY, LDConfig.DEFAULT));
50+
51+
random = new Random();
52+
53+
for (int i = 0; i < TEST_EVENTS_COUNT; i++) {
54+
String flagKey = "flag" + random.nextInt(FLAG_COUNT);
55+
int version = random.nextInt(FLAG_VERSIONS) + 1;
56+
int variation = random.nextInt(FLAG_VARIATIONS);
57+
for (boolean trackEvents: new boolean[] { false, true }) {
58+
Event.FeatureRequest event = new Event.FeatureRequest(
59+
System.currentTimeMillis(),
60+
flagKey,
61+
BASIC_USER,
62+
version,
63+
variation,
64+
LDValue.of(variation),
65+
LDValue.ofNull(),
66+
null,
67+
null,
68+
trackEvents,
69+
0,
70+
false
71+
);
72+
(trackEvents ? featureRequestEventsWithTracking : featureRequestEventsWithoutTracking).add(event);
73+
}
74+
}
75+
}
76+
77+
public String randomFlagKey() {
78+
return "flag" + random.nextInt(FLAG_COUNT);
79+
}
80+
81+
public int randomFlagVersion() {
82+
return random.nextInt(FLAG_VERSIONS) + 1;
83+
}
84+
85+
public int randomFlagVariation() {
86+
return random.nextInt(FLAG_VARIATIONS);
87+
}
88+
}
89+
90+
@Benchmark
91+
public void summarizeFeatureRequestEvents(BenchmarkInputs inputs) throws Exception {
92+
for (Event.FeatureRequest event: inputs.featureRequestEventsWithoutTracking) {
93+
inputs.eventProcessor.sendEvent(event);
94+
}
95+
inputs.eventProcessor.flush();
96+
inputs.eventSender.awaitEvents();
97+
}
98+
99+
@Benchmark
100+
public void featureRequestEventsWithFullTracking(BenchmarkInputs inputs) throws Exception {
101+
for (Event.FeatureRequest event: inputs.featureRequestEventsWithTracking) {
102+
inputs.eventProcessor.sendEvent(event);
103+
}
104+
inputs.eventProcessor.flush();
105+
inputs.eventSender.awaitEvents();
106+
}
107+
108+
@Benchmark
109+
public void customEvents(BenchmarkInputs inputs) throws Exception {
110+
for (int i = 0; i < TEST_EVENTS_COUNT; i++) {
111+
inputs.eventProcessor.sendEvent(CUSTOM_EVENT);
112+
}
113+
inputs.eventProcessor.flush();
114+
inputs.eventSender.awaitEvents();
115+
}
116+
117+
private static final class MockEventSender implements EventSender {
118+
private static final Result RESULT = new Result(true, false, null);
119+
private static final CountDownLatch counter = new CountDownLatch(1);
120+
121+
@Override
122+
public void close() throws IOException {}
123+
124+
@Override
125+
public Result sendEventData(EventDataKind arg0, String arg1, int arg2, URI arg3) {
126+
counter.countDown();
127+
return RESULT;
128+
}
129+
130+
public void awaitEvents() throws InterruptedException {
131+
counter.await();
132+
}
133+
}
134+
135+
private static final class MockEventSenderFactory implements EventSenderFactory {
136+
private final MockEventSender instance;
137+
138+
MockEventSenderFactory(MockEventSender instance) {
139+
this.instance = instance;
140+
}
141+
142+
@Override
143+
public EventSender createEventSender(String arg0, HttpConfiguration arg1) {
144+
return instance;
145+
}
146+
}
147+
}

0 commit comments

Comments
 (0)