Skip to content

Commit 3e778a5

Browse files
emyllerkhvn26
andauthored
feat(CI): Set up CodSpeed (#241)
Co-authored-by: Kim Gustyr <kim.gustyr@flagsmith.com>
1 parent 773fb3e commit 3e778a5

7 files changed

Lines changed: 290 additions & 1 deletion

File tree

‎.github/workflows/codspeed.yml‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: CodSpeed
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
workflow_dispatch:
9+
10+
permissions:
11+
contents: read # Check out the repository and its submodules
12+
id-token: write # Authenticate result uploads to CodSpeed via OIDC
13+
14+
jobs:
15+
benchmarks:
16+
name: Run benchmarks
17+
runs-on: codspeed-macro
18+
19+
steps:
20+
- name: Checkout repository
21+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
22+
with:
23+
submodules: recursive
24+
persist-credentials: false
25+
26+
- name: Set up JDK
27+
uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0
28+
with:
29+
distribution: temurin
30+
java-version: 21
31+
cache: maven
32+
33+
- name: Set up Maven
34+
uses: stCarolas/setup-maven@12eb41b233df95d49b0c11fc1b5bc8312e5d4ce0 # v5.1
35+
with:
36+
maven-version: 3.10.0
37+
38+
- name: Run benchmarks
39+
uses: CodSpeedHQ/action@78102a468f96d8e0ccb92d0ec49425753fc42062 # v5.6.0
40+
with:
41+
mode: walltime
42+
run: make benchmark

‎.gitmodules‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
[submodule "src/test/java/com/flagsmith/flagengine/enginetestdata"]
22
path = src/test/java/com/flagsmith/flagengine/enginetestdata
33
url = git@github.com:Flagsmith/engine-test-data.git
4-
branch = v3.7.0
4+
branch = v3.7.0
5+
[submodule "third-party/codspeed-jvm"]
6+
path = third-party/codspeed-jvm
7+
url = https://github.com/CodSpeedHQ/codspeed-jvm.git
8+
[submodule "third-party/sdk-benchmarks"]
9+
path = third-party/sdk-benchmarks
10+
url = https://github.com/Flagsmith/sdk-benchmarks.git
11+
branch = 0.0.1

‎Makefile‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
BENCHMARKS_JAR ?= target/benchmarks.jar
2+
CODSPEED_JVM := third-party/codspeed-jvm
3+
SDK_BENCHMARKS := third-party/sdk-benchmarks
4+
5+
$(CODSPEED_JVM)/gradlew:
6+
git submodule update --init -- $(CODSPEED_JVM)
7+
git -C $(CODSPEED_JVM) submodule update --init -- jmh-fork/jmh-core/native-instrument-hooks/instrument-hooks
8+
9+
$(SDK_BENCHMARKS)/environments:
10+
git submodule update --init -- $(SDK_BENCHMARKS)
11+
12+
$(BENCHMARKS_JAR): pom.xml $(shell find src/main src/jmh -type f) $(wildcard $(SDK_BENCHMARKS)/environments/*.json) | $(CODSPEED_JVM)/gradlew $(SDK_BENCHMARKS)/environments
13+
cd third-party/codspeed-jvm && ./gradlew -p jmh-fork publishToMavenLocal
14+
mvn --batch-mode --no-transfer-progress -P benchmarks -DskipTests package
15+
16+
.PHONY: benchmark
17+
benchmark: $(BENCHMARKS_JAR)
18+
java -jar $(BENCHMARKS_JAR)

‎pom.xml‎

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,10 @@
301301
<failsOnError>true</failsOnError>
302302
<linkXRef>true</linkXRef>
303303
<violationSeverity>warning</violationSeverity>
304+
<sourceDirectories>
305+
<sourceDirectory>${project.build.sourceDirectory}</sourceDirectory>
306+
<sourceDirectory>${project.basedir}/src/jmh/java</sourceDirectory>
307+
</sourceDirectories>
304308
</configuration>
305309
<executions>
306310
<execution>
@@ -388,5 +392,97 @@
388392
<okhttp.mockwebserver.version>4.12.0</okhttp.mockwebserver.version>
389393
</properties>
390394
</profile>
395+
<profile>
396+
<!-- CodSpeed JMH benchmarks (JDK 21+), see https://codspeed.io/docs/benchmarks/java -->
397+
<id>benchmarks</id>
398+
<properties>
399+
<codspeed.jmh.version>0.2.1</codspeed.jmh.version>
400+
</properties>
401+
<dependencies>
402+
<dependency>
403+
<groupId>io.codspeed.jmh</groupId>
404+
<artifactId>jmh-core</artifactId>
405+
<version>${codspeed.jmh.version}</version>
406+
</dependency>
407+
</dependencies>
408+
<build>
409+
<plugins>
410+
<plugin>
411+
<groupId>org.codehaus.mojo</groupId>
412+
<artifactId>build-helper-maven-plugin</artifactId>
413+
<version>3.6.2</version>
414+
<executions>
415+
<execution>
416+
<id>add-jmh-sources</id>
417+
<phase>generate-sources</phase>
418+
<goals>
419+
<goal>add-source</goal>
420+
</goals>
421+
<configuration>
422+
<sources>
423+
<source>${project.basedir}/src/jmh/java</source>
424+
</sources>
425+
</configuration>
426+
</execution>
427+
<execution>
428+
<id>add-jmh-resources</id>
429+
<phase>generate-resources</phase>
430+
<goals>
431+
<goal>add-resource</goal>
432+
</goals>
433+
<configuration>
434+
<resources>
435+
<resource>
436+
<directory>${project.basedir}/third-party/sdk-benchmarks</directory>
437+
<includes>
438+
<include>environments/*.json</include>
439+
</includes>
440+
</resource>
441+
</resources>
442+
</configuration>
443+
</execution>
444+
</executions>
445+
</plugin>
446+
<plugin>
447+
<groupId>org.apache.maven.plugins</groupId>
448+
<artifactId>maven-compiler-plugin</artifactId>
449+
<configuration>
450+
<source>21</source>
451+
<target>21</target>
452+
<annotationProcessorPaths combine.children="append">
453+
<path>
454+
<groupId>io.codspeed.jmh</groupId>
455+
<artifactId>jmh-generator-annprocess</artifactId>
456+
<version>${codspeed.jmh.version}</version>
457+
</path>
458+
</annotationProcessorPaths>
459+
</configuration>
460+
</plugin>
461+
<plugin>
462+
<groupId>org.apache.maven.plugins</groupId>
463+
<artifactId>maven-shade-plugin</artifactId>
464+
<version>3.6.2</version>
465+
<executions>
466+
<execution>
467+
<phase>package</phase>
468+
<goals>
469+
<goal>shade</goal>
470+
</goals>
471+
<configuration>
472+
<outputFile>${project.build.directory}/benchmarks.jar</outputFile>
473+
<createDependencyReducedPom>false</createDependencyReducedPom>
474+
<transformers>
475+
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
476+
<mainClass>org.openjdk.jmh.Main</mainClass>
477+
</transformer>
478+
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
479+
</transformers>
480+
</configuration>
481+
</execution>
482+
</executions>
483+
</plugin>
484+
</plugins>
485+
</build>
486+
</profile>
391487
</profiles>
392488
</project>
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package com.flagsmith;
2+
3+
import com.flagsmith.config.FlagsmithConfig;
4+
import com.flagsmith.exceptions.FlagsmithClientError;
5+
import com.flagsmith.flagengine.EvaluationContext;
6+
import com.flagsmith.models.Segment;
7+
import java.io.IOException;
8+
import java.io.InputStream;
9+
import java.util.List;
10+
import java.util.Map;
11+
import okhttp3.Interceptor;
12+
import okhttp3.MediaType;
13+
import okhttp3.Protocol;
14+
import okhttp3.Response;
15+
import okhttp3.ResponseBody;
16+
import org.openjdk.jmh.annotations.Benchmark;
17+
import org.openjdk.jmh.annotations.Param;
18+
import org.openjdk.jmh.annotations.Scope;
19+
import org.openjdk.jmh.annotations.Setup;
20+
import org.openjdk.jmh.annotations.State;
21+
import org.openjdk.jmh.annotations.TearDown;
22+
23+
/**
24+
* Benchmarks a locally evaluating SDK client against environment documents tiered by size.
25+
*/
26+
@State(Scope.Benchmark)
27+
public class SdkBenchmark {
28+
private static final MediaType JSON = MediaType.get("application/json");
29+
private static final String IDENTITY_OVERRIDES_IDENTIFIER = "identity-override-1";
30+
private static final String IDENTIFIER = "user-1";
31+
private static final Map<String, Object> TRAITS = Map.of(
32+
"plan", "plan-7",
33+
"age", 30,
34+
"country", "GB",
35+
"version", "2.5.1",
36+
"email", "jane@example.com",
37+
"user_id", 12,
38+
"beta", true,
39+
"score", 95,
40+
"tier", "pro",
41+
"device", "iPhone 15");
42+
43+
@Param({"small", "medium", "large"})
44+
public String environment;
45+
46+
private FlagsmithClient client;
47+
48+
/**
49+
* Build a locally evaluating client serving the environment document from memory.
50+
*/
51+
@Setup
52+
public void buildClient() throws IOException {
53+
byte[] environmentDocument;
54+
try (InputStream stream = getClass().getResourceAsStream(
55+
"/environments/" + environment + ".json")) {
56+
environmentDocument = stream.readAllBytes();
57+
}
58+
59+
client = FlagsmithClient.newBuilder()
60+
.setApiKey("ser.benchmark")
61+
.withConfiguration(FlagsmithConfig.newBuilder()
62+
.baseUri("http://flagsmith.benchmark/api/v1/")
63+
.addHttpInterceptor(serveEnvironmentDocument(environmentDocument))
64+
.withLocalEvaluation(true)
65+
.withEnvironmentRefreshIntervalSeconds(86400) // Refreshed by benchmarks only
66+
.build())
67+
.build();
68+
}
69+
70+
@TearDown
71+
public void closeClient() {
72+
client.close();
73+
}
74+
75+
/**
76+
* Get a feature value from the environment flags.
77+
*/
78+
@Benchmark
79+
public Object getEnvironmentFlags() throws FlagsmithClientError {
80+
return client.getEnvironmentFlags().getFeatureValue("feature_1");
81+
}
82+
83+
/**
84+
* Check a feature for an identity with identity overrides.
85+
*/
86+
@Benchmark
87+
public boolean getIdentityFlagsWithIdentityOverrides() throws FlagsmithClientError {
88+
return client.getIdentityFlags(IDENTITY_OVERRIDES_IDENTIFIER).isFeatureEnabled("feature_8");
89+
}
90+
91+
/**
92+
* Get a feature value for an identity without traits.
93+
*/
94+
@Benchmark
95+
public Object getIdentityFlagsNoTraits() throws FlagsmithClientError {
96+
return client.getIdentityFlags(IDENTIFIER).getFeatureValue("feature_1");
97+
}
98+
99+
/**
100+
* Get a feature value for an identity with traits.
101+
*/
102+
@Benchmark
103+
public Object getIdentityFlagsWithTraits() throws FlagsmithClientError {
104+
return client.getIdentityFlags(IDENTIFIER, TRAITS).getFeatureValue("feature_1");
105+
}
106+
107+
/**
108+
* List the segments of an identity with traits.
109+
*/
110+
@Benchmark
111+
public List<Segment> getIdentitySegmentsWithTraits() throws FlagsmithClientError {
112+
return client.getIdentitySegments(IDENTIFIER, TRAITS);
113+
}
114+
115+
private static Interceptor serveEnvironmentDocument(byte[] environmentDocument) {
116+
return chain -> new Response.Builder()
117+
.request(chain.request())
118+
.protocol(Protocol.HTTP_1_1)
119+
.code(200)
120+
.message("OK")
121+
.body(ResponseBody.create(environmentDocument, JSON))
122+
.build();
123+
}
124+
}

‎third-party/codspeed-jvm‎

Submodule codspeed-jvm added at af05f6f

‎third-party/sdk-benchmarks‎

Submodule sdk-benchmarks added at 1b05624

0 commit comments

Comments
 (0)