Skip to content

Commit 8d219c0

Browse files
committed
Merge branch 'master' into 242-fix-concurrency-issue
# Conflicts: # docs/RELEASE_NOTES.md
2 parents 967251b + 5ea07c4 commit 8d219c0

24 files changed

Lines changed: 840 additions & 95 deletions

File tree

.travis.yml

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,28 @@
11
sudo: false
2-
addons:
3-
apt:
4-
packages:
5-
- oracle-java8-installer
62
language: java
73
dist: trusty
84
jdk:
95
- openjdk8
106
install: true # skips travis' default installation step which executes gradle assemble.
11-
script: ./gradlew clean setReleaseVersion build
7+
jobs:
8+
include:
9+
- stage: benchmark#1
10+
if: tag IS present
11+
script: ./gradlew clean setLibraryVersion benchmark benchmarkCommit
12+
- stage: benchmark#2
13+
if: tag IS present
14+
script: ./gradlew clean setLibraryVersion benchmark benchmarkCommit
15+
- stage: benchmark#3
16+
if: tag IS present
17+
script: ./gradlew clean setLibraryVersion benchmark benchmarkCommit
18+
- stage: benchmark#4
19+
if: tag IS present
20+
script: ./gradlew clean setLibraryVersion benchmark benchmarkCommit
21+
- stage: benchmark#5
22+
if: tag IS present
23+
script: ./gradlew clean setLibraryVersion benchmark benchmarkCommit
24+
- stage: full build
25+
script: ./gradlew clean setLibraryVersion build
1226
# The before_cache and the cache steps cache the gradle installation on travis.
1327
before_cache:
1428
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock

build.gradle

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ plugins {
22
id 'com.jfrog.bintray' version '1.7.3'
33
id 'org.ajoberstar.git-publish' version '0.3.2'
44
id 'com.adarshr.test-logger' version '1.1.2'
5+
id 'org.ajoberstar.grgit' version '2.1.0'
56
}
67

78
apply from: "$rootDir/gradle-scripts/plugins.gradle"
@@ -11,6 +12,7 @@ apply from: "$rootDir/gradle-scripts/package.gradle"
1112
apply from: "$rootDir/gradle-scripts/java-compile.gradle"
1213
apply from: "$rootDir/gradle-scripts/repositories.gradle"
1314
apply from: "$rootDir/gradle-scripts/integration-tests.gradle"
15+
apply from: "$rootDir/gradle-scripts/benchmark.gradle"
1416
apply from: "$rootDir/gradle-scripts/test-logger.gradle"
1517
apply from: "$rootDir/gradle-scripts/dependencies.gradle"
1618
apply from: "$rootDir/gradle-scripts/checkstyle.gradle"
@@ -21,5 +23,5 @@ apply from: "$rootDir/gradle-scripts/maven-publish.gradle"
2123
apply from: "$rootDir/gradle-scripts/bintray-publish.gradle"
2224
apply from: "$rootDir/gradle-scripts/oss-publish.gradle"
2325
apply from: "$rootDir/gradle-scripts/javadocs-publish.gradle"
24-
apply from: "$rootDir/gradle-scripts/set-release-version.gradle"
26+
apply from: "$rootDir/gradle-scripts/set-library-version.gradle"
2527
apply from: "$rootDir/gradle-scripts/execution-order.gradle"

config/checkstyle/suppressions.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@
77
<suppressions>
88
<!-- Suppressions for testing code -->
99
<suppress checks="JavadocMethod"
10-
files="[/\\]src[/\\]test[/\\]java[/\\]|[/\\]src[/\\]integration-test[/\\]java[/\\]"/>
10+
files="[/\\]src[/\\]test[/\\]java[/\\]|[/\\]src[/\\]integration-test[/\\]java[/\\]|[/\\]src[/\\]benchmark[/\\]java[/\\]"/>
1111
</suppressions>

docs/BENCHMARKS.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Benchmarks
2+
3+
## Setup
4+
5+
1. Benchmarks are run by JUnit as separate source set just like main, test and integration-test. The benchmarks can be
6+
found [here](/src/benchmark/java/com/commercetools/sync/benchmark).
7+
8+
2. Every benchmark writes it's results as JSON to
9+
[benchmarks.json](https://commercetools.github.io/commercetools-sync-java/benchmarks/benchmarks.json) which is saved in
10+
the gh-pages branch of the repo.
11+
12+
3. Every time a release is made or the build has a git tag, the benchmarks are run and the results are added to
13+
[benchmarks.json](https://commercetools.github.io/commercetools-sync-java/benchmarks/benchmarks.json).
14+
15+
4. Using Travis stages, each benchmark is run 5 times, producing 5 results in which the averages of the results is
16+
calculated for more accurate results.
17+
18+
5. The average results in [benchmarks.json](https://commercetools.github.io/commercetools-sync-java/benchmarks/benchmarks.json)
19+
are used to display the results in the form of this [graph](https://commercetools.github.io/commercetools-sync-java/benchmarks/).
20+
21+
22+
## Results
23+
24+
Results as json can be found [here](https://commercetools.github.io/commercetools-sync-java/benchmarks/benchmarks.json).
25+
Results as a chart can be found [here](https://commercetools.github.io/commercetools-sync-java/benchmarks/).

docs/RELEASE_NOTES.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
3434

3535

36+
- [v1.0.0-M10 - Feb 13, 2018](#v100-m10----feb-13-2018)
3637
- [v1.0.0-M9 - Jan 22, 2018](#v100-m9----jan-22-2018)
3738
- [v1.0.0-M8 - Dec 29, 2017](#v100-m8----dec-29-2017)
3839
- [v1.0.0-M7 - Dec 15, 2017](#v100-m7----dec-15-2017)
@@ -46,18 +47,18 @@
4647

4748
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
4849

49-
<!--
50-
### v1.0.0-M10 - Feb 07, 2018
50+
### v1.0.0-M10 - Feb 13, 2018
5151
[Commits](https://github.com/commercetools/commercetools-sync-java/compare/v1.0.0-M9...v1.0.0-M10) |
5252
[Javadoc](https://commercetools.github.io/commercetools-sync-java/v/v1.0.0-M10/) |
5353
[Jar](https://bintray.com/commercetools/maven/commercetools-sync-java/v1.0.0-M10)
5454

55+
**New Features** (1)
56+
- **Commons** - Added [benchmarking setup](/docs/BENCHMARKS.md) for the library on every release. [#155](https://github.com/commercetools/commercetools-sync-java/issues/155)
5557

56-
**Changes** (1)
58+
**Changes** (3)
5759
- **Commons** - Statistics counters are now of type `AtomicInteger` instead of int to support conccurency. [#242](https://github.com/commercetools/commercetools-sync-java/issues/242)
5860
- **Category Sync** - `categoryKeysWithMissingParents` in the `CategorySyncStatistics` is now of type `ConcurrentHashMap<String, Set<String>` instead of `Map<String, List<String>`. [#242](https://github.com/commercetools/commercetools-sync-java/issues/242)
5961
- **Category Sync** - `CategorySyncStatistics` now exposes the methods `removeChildCategoryKeyFromMissingParentsMap`, `getMissingParentKey` and `putMissingParentCategoryChildKey` to support manipulating `categoryKeysWithMissingParents` map. [#242](https://github.com/commercetools/commercetools-sync-java/issues/242)
60-
-->
6162

6263
### v1.0.0-M9 - Jan 22, 2018
6364
[Commits](https://github.com/commercetools/commercetools-sync-java/compare/v1.0.0-M8...v1.0.0-M9) |

gradle-scripts/benchmark.gradle

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
sourceSets {
2+
benchmark {
3+
java {
4+
compileClasspath += main.output + test.output + integrationTest.output
5+
runtimeClasspath += main.output + test.output + integrationTest.output
6+
srcDir 'src/benchmark/java'
7+
}
8+
resources.srcDir 'src/benchmark/resources'
9+
}
10+
}
11+
12+
configurations {
13+
benchmarkImplementation.extendsFrom implementation, testImplementation
14+
}
15+
16+
task benchmark(type: Test) {
17+
doFirst{
18+
grgit.clone(dir: 'tmp_git_dir/', uri: scmProjectUrl, checkout: true, refToCheckout: 'gh-pages')
19+
}
20+
testClassesDirs = sourceSets.benchmark.output.classesDirs
21+
classpath = sourceSets.benchmark.runtimeClasspath
22+
outputs.upToDateWhen { false }
23+
}
24+
25+
task benchmarkCommit() {
26+
doLast{
27+
def git = grgit.open(dir: System.getenv('CI_BUILD_DIR') + '/tmp_git_dir')
28+
git.add(patterns: ['benchmarks/benchmarks.json'])
29+
git.commit(message: '#155: Add new benchmark result.')
30+
git.push()
31+
git.close()
32+
}
33+
}

gradle-scripts/execution-order.gradle

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,7 @@ check.dependsOn jacocoTestReport
4545
// Ensure jacocoTestCoverageVerification and jacocoTestReport run after integrationTest
4646
jacocoTestCoverageVerification.mustRunAfter integrationTest
4747
jacocoTestReport.mustRunAfter integrationTest
48-
// Ensure build runs after setReleaseVersion
49-
build.mustRunAfter setReleaseVersion
48+
// Ensure build runs after setLibraryVersion
49+
build.mustRunAfter setLibraryVersion
50+
// Ensure benchmark results are only committed runs after the benchmarks are run
51+
benchmarkCommit.mustRunAfter benchmark

gradle-scripts/javadocs-publish.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ gitPublish {
1919
// what to keep in the existing branch (include=keep)
2020
preserve {
2121
include 'v/**'
22+
include 'benchmarks/**'
2223
exclude 'v/test**' // always remove documentation of test releases
2324
}
2425

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
task setReleaseVersion {
1+
task setLibraryVersion {
22
description 'If the env var "TRAVIS_TAG" is set, injects the value in the ' +
33
'src/main/java/com/commercetools/sync/commons/utils/SyncSolutionInfo.java. Otherwise, if the env var is not ' +
44
'set it sets the version to the value "dev-version". Note: Should only be executed before compilation in ' +
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package com.commercetools.sync.benchmark;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
6+
import com.fasterxml.jackson.databind.node.ObjectNode;
7+
8+
import javax.annotation.Nonnull;
9+
import java.io.IOException;
10+
import java.nio.charset.Charset;
11+
import java.nio.charset.StandardCharsets;
12+
import java.nio.file.Files;
13+
import java.nio.file.Paths;
14+
import java.util.Iterator;
15+
import java.util.List;
16+
import java.util.Optional;
17+
import java.util.Spliterator;
18+
import java.util.stream.Collectors;
19+
import java.util.stream.Stream;
20+
21+
import static java.util.Optional.ofNullable;
22+
import static java.util.Spliterators.spliteratorUnknownSize;
23+
import static java.util.stream.StreamSupport.stream;
24+
25+
public class BenchmarkUtils {
26+
private static final String BENCHMARK_RESULTS_FILE_NAME = "benchmarks.json";
27+
private static final String BENCHMARK_RESULTS_FILE_DIR = ofNullable(System.getenv("CI_BUILD_DIR"))
28+
.map(path -> path + "/tmp_git_dir/benchmarks/").orElse("");
29+
private static final String BENCHMARK_RESULTS_FILE_PATH = BENCHMARK_RESULTS_FILE_DIR + BENCHMARK_RESULTS_FILE_NAME;
30+
private static final Charset UTF8_CHARSET = StandardCharsets.UTF_8;
31+
private static final String EXECUTION_TIMES = "executionTimes";
32+
private static final String AVERAGE = "average";
33+
private static final String DIFF = "diff";
34+
35+
static final String PRODUCT_SYNC = "productSync";
36+
static final String INVENTORY_SYNC = "inventorySync";
37+
static final String CATEGORY_SYNC = "categorySync";
38+
static final String CREATES_ONLY = "createsOnly";
39+
static final String UPDATES_ONLY = "updatesOnly";
40+
static final String CREATES_AND_UPDATES = "mix";
41+
static final int THRESHOLD = 120000; //120 seconds in milliseconds
42+
static final int NUMBER_OF_RESOURCE_UNDER_TEST = 10000;
43+
44+
45+
static void saveNewResult(@Nonnull final String version,
46+
@Nonnull final String sync,
47+
@Nonnull final String benchmark,
48+
final double newResult) throws IOException {
49+
50+
final JsonNode rootNode = new ObjectMapper().readTree(getFileContent(BENCHMARK_RESULTS_FILE_PATH));
51+
final JsonNode withNewResult = addNewResult(rootNode, version, sync, benchmark, newResult);
52+
writeToFile(withNewResult.toString(), BENCHMARK_RESULTS_FILE_PATH);
53+
}
54+
55+
private static JsonNode addNewResult(@Nonnull final JsonNode originalRoot,
56+
@Nonnull final String version,
57+
@Nonnull final String sync,
58+
@Nonnull final String benchmark,
59+
final double newResult) {
60+
ObjectNode rootNode = (ObjectNode) originalRoot;
61+
ObjectNode versionNode = (ObjectNode) rootNode.get(version);
62+
63+
// If version doesn't exist yet, create a new JSON object for the new version.
64+
if (versionNode == null) {
65+
rootNode = createVersionNode(rootNode, version);
66+
versionNode = (ObjectNode) rootNode.get(version);
67+
}
68+
69+
final ObjectNode syncNode = (ObjectNode) versionNode.get(sync);
70+
final ObjectNode benchmarkNode = (ObjectNode) syncNode.get(benchmark);
71+
72+
// Get current list of execution times for the specified benchmark of the specified sync module
73+
// of the specified version.
74+
final List<JsonNode> results = toList(benchmarkNode.get(EXECUTION_TIMES).elements());
75+
76+
// Add new result.
77+
results.add(JsonNodeFactory.instance.numberNode(newResult));
78+
benchmarkNode.set(EXECUTION_TIMES, JsonNodeFactory.instance.arrayNode().addAll(results));
79+
80+
// Compute new average and add to JSON Object
81+
final double averageResult = calculateAvg(results);
82+
benchmarkNode.set(AVERAGE, JsonNodeFactory.instance.numberNode(averageResult));
83+
84+
// Compute new diff from the last version.
85+
final double diff = calculateDiff(rootNode, version, sync, benchmark, averageResult);
86+
benchmarkNode.set(DIFF, JsonNodeFactory.instance.numberNode(diff));
87+
88+
final JsonNode newSyncNode = syncNode.set(benchmark, benchmarkNode);
89+
final JsonNode newVersionNode = versionNode.set(sync, newSyncNode);
90+
final JsonNode newRoot = rootNode.set(version, newVersionNode);
91+
return newRoot;
92+
}
93+
94+
@Nonnull
95+
private static <T> List<T> toList(@Nonnull final Iterator<T> iterator) {
96+
return toStream(iterator).collect(Collectors.toList());
97+
}
98+
99+
@Nonnull
100+
private static <T> Stream<T> toStream(@Nonnull final Iterator<T> iterator) {
101+
return stream(spliteratorUnknownSize(iterator, Spliterator.ORDERED), false);
102+
}
103+
104+
private static double calculateAvg(@Nonnull final List<JsonNode> results) {
105+
return results.stream().mapToDouble(JsonNode::asLong).average().orElse(0);
106+
}
107+
108+
private static ObjectNode createVersionNode(@Nonnull final ObjectNode rootNode, @Nonnull final String version) {
109+
final ObjectNode newVersionNode = createSyncNode(
110+
createSyncNode(createSyncNode(JsonNodeFactory.instance.objectNode(),
111+
PRODUCT_SYNC), INVENTORY_SYNC), CATEGORY_SYNC);
112+
return (ObjectNode) rootNode.set(version, newVersionNode);
113+
}
114+
115+
private static ObjectNode createSyncNode(@Nonnull final ObjectNode versionNode,
116+
@Nonnull final String sync) {
117+
final ObjectNode newSyncNode = createBenchmarkNode(
118+
createBenchmarkNode(
119+
createBenchmarkNode(JsonNodeFactory.instance.objectNode(), CREATES_ONLY), UPDATES_ONLY),
120+
CREATES_AND_UPDATES);
121+
return (ObjectNode) versionNode.set(sync, newSyncNode);
122+
}
123+
124+
private static ObjectNode createBenchmarkNode(@Nonnull final ObjectNode syncNode,
125+
@Nonnull final String benchmark) {
126+
final ObjectNode newBenchmarkNode = JsonNodeFactory.instance.objectNode();
127+
newBenchmarkNode.set(EXECUTION_TIMES, JsonNodeFactory.instance.arrayNode());
128+
newBenchmarkNode.set(AVERAGE, JsonNodeFactory.instance.numberNode(0));
129+
newBenchmarkNode.set(DIFF, JsonNodeFactory.instance.numberNode(0));
130+
131+
syncNode.set(benchmark, newBenchmarkNode);
132+
return syncNode;
133+
}
134+
135+
static double calculateDiff(@Nonnull final String version,
136+
@Nonnull final String sync,
137+
@Nonnull final String benchmark,
138+
final double average) throws IOException {
139+
final JsonNode rootNode = new ObjectMapper().readTree(getFileContent(BENCHMARK_RESULTS_FILE_PATH));
140+
return calculateDiff(rootNode, version, sync, benchmark, average);
141+
}
142+
143+
private static double calculateDiff(@Nonnull final JsonNode originalRoot,
144+
@Nonnull final String version,
145+
@Nonnull final String sync,
146+
@Nonnull final String benchmark,
147+
final double average) {
148+
return getLatestVersionName(originalRoot, version)
149+
.map(latestVersionName -> originalRoot.get(latestVersionName)
150+
.get(sync)
151+
.get(benchmark)
152+
.get(AVERAGE))
153+
.map(latestAverageNode -> average - latestAverageNode.asDouble())
154+
// if there is no latest version - the current average is the diff.
155+
.orElse(average);
156+
}
157+
158+
private static Optional<String> getLatestVersionName(@Nonnull final JsonNode originalRoot,
159+
@Nonnull final String currentVersionName) {
160+
return toStream(originalRoot.fieldNames()).reduce((firstVersion, secondVersion) ->
161+
!currentVersionName.equals(secondVersion) ? secondVersion : firstVersion);
162+
}
163+
164+
165+
private static String getFileContent(@Nonnull final String path) throws IOException {
166+
final byte[] fileBytes = Files.readAllBytes(Paths.get(path));
167+
return new String(fileBytes, UTF8_CHARSET);
168+
}
169+
170+
private static void writeToFile(@Nonnull final String content, @Nonnull final String path) throws IOException {
171+
Files.write(Paths.get(path), content.getBytes(UTF8_CHARSET));
172+
}
173+
}

0 commit comments

Comments
 (0)