Skip to content

Commit 74102d4

Browse files
committed
test: add benchmarking using jmh
1 parent 142d7e8 commit 74102d4

File tree

4 files changed

+171
-0
lines changed

4 files changed

+171
-0
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import java.time.Instant
33
plugins {
44
`java-library`
55
alias(libs.plugins.shadow) apply false
6+
alias(libs.plugins.publisher) apply false
67

78
eclipse
89
idea

common/build.gradle.kts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,45 @@
11
import com.vanniktech.maven.publish.JavaLibrary
22
import com.vanniktech.maven.publish.JavadocJar
33
import com.vanniktech.maven.publish.SonatypeHost
4+
import me.champeau.jmh.JMHPlugin
5+
import me.champeau.jmh.JmhParameters
46

57
plugins {
8+
alias(libs.plugins.jmh)
69
alias(libs.plugins.publisher)
710
}
811

12+
plugins.withType<JMHPlugin> {
13+
extensions.configure(JmhParameters::class) {
14+
jmhVersion = libs.versions.jmh.get()
15+
}
16+
tasks.compileJmhJava {
17+
dependsOn(tasks.compileTestJava, tasks.processTestResources) // avoid implicit task dependencies
18+
}
19+
tasks.named(JMHPlugin.getJMH_TASK_COMPILE_GENERATED_CLASSES_NAME(), JavaCompile::class) {
20+
classpath += configurations.getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME).incoming.files
21+
}
22+
}
23+
924
dependencies {
25+
jmh(rootProject.libs.bundles.jmh)
26+
}
27+
28+
tasks {
29+
jmh {
30+
jmhVersion.set(rootProject.libs.jmh.core.get().version)
31+
32+
benchmarkMode.set(listOf("SampleTime"))
33+
fork.set(2)
34+
warmupIterations.set(5)
35+
iterations.set(5)
36+
warmup.set("1s")
37+
timeOnIteration.set("1s")
38+
timeUnit.set("us")
39+
resultFormat.set("JSON")
40+
41+
failOnError.set(false)
42+
}
1043
}
1144

1245
mavenPublishing {
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package io.github.milkdrinkers.colorparser.common;
2+
3+
import io.github.milkdrinkers.colorparser.common.mock.MockComponentBuilder;
4+
import io.github.milkdrinkers.colorparser.common.mock.engine.MockParserEngine;
5+
import io.github.milkdrinkers.colorparser.common.mock.engine.MockParserEngineBuilder;
6+
import net.kyori.adventure.text.Component;
7+
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
8+
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
9+
import org.openjdk.jmh.annotations.*;
10+
import org.openjdk.jmh.infra.Blackhole;
11+
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
15+
@SuppressWarnings("unused")
16+
@State(Scope.Benchmark)
17+
public class ComponentBuilderBenchmark {
18+
private static final String SIMPLE_CONTENT = "<red>Hello <bold>World</bold></red>";
19+
private static final String COMPLEX_CONTENT = "<gradient:red:blue>Complex <hover:show_text:'<green>Tooltip</green>'>hover text</hover> with <click:run_command:'/test'>click</click> and <player_name> placeholder</gradient>";
20+
private static final String LEGACY_CONTENT = "&cRed &lBold &r&9Blue &o&nUnderlined";
21+
22+
private MockParserEngine engine;
23+
private MockComponentBuilder reusableBuilder;
24+
private List<TagResolver> tagResolvers;
25+
26+
@Setup(Level.Trial)
27+
public void setupTrial() {
28+
engine = new MockParserEngineBuilder().build();
29+
reusableBuilder = engine.parse(SIMPLE_CONTENT);
30+
31+
// Pre-create tag resolvers for TagResolver benchmark
32+
tagResolvers = new ArrayList<>();
33+
for (int i = 0; i < 20; i++) {
34+
tagResolvers.add(Placeholder.parsed("tag_" + i, "<color:#ff" + String.format("%04x", i) + ">Tag " + i + "</color>"));
35+
}
36+
}
37+
38+
@Benchmark
39+
public Component benchmarkSimpleComponentBuilding() {
40+
final MockComponentBuilder b = engine.parse(SIMPLE_CONTENT);
41+
return b.build();
42+
}
43+
44+
@Benchmark
45+
public Component benchmarkComplexComponentWithPlaceholders() {
46+
final MockComponentBuilder b = engine.parse(COMPLEX_CONTENT);
47+
return b.with("player_name", "<gold>TestPlayer</gold>")
48+
.with("server_name", "<blue>TestServer</blue>")
49+
.with("timestamp", "<gray>2025-06-12</gray>")
50+
.build();
51+
}
52+
53+
@Benchmark
54+
public Component benchmarkLegacyColorProcessing() {
55+
final MockComponentBuilder b = engine.parse(LEGACY_CONTENT);
56+
return b.legacy().build();
57+
}
58+
59+
@Benchmark
60+
public Component benchmarkBuilderReuse() {
61+
reusableBuilder.with("test1", "value1").build();
62+
return reusableBuilder.with("test2", "value2").build();
63+
}
64+
65+
@Benchmark
66+
public Component benchmarkMemoryAllocation(Blackhole bh) {
67+
final List<Component> components = new ArrayList<>();
68+
69+
for (int i = 0; i < 100; i++) {
70+
final MockComponentBuilder b = engine.parse("<red>" + i + "</red>");
71+
Component component = b.build();
72+
components.add(component);
73+
bh.consume(component);
74+
}
75+
76+
return components.getLast();
77+
}
78+
79+
@Benchmark
80+
public Component benchmarkTagResolvers() {
81+
final MockComponentBuilder b = engine.parse(generateContentWithTags(20));
82+
tagResolvers.forEach(b::tag);
83+
return b.build();
84+
}
85+
86+
@State(Scope.Benchmark)
87+
public static class PlaceholderState {
88+
@Param({"1", "5", "10", "25", "50", "100"})
89+
public int placeholderCount;
90+
91+
public MockParserEngine engine;
92+
public String content;
93+
94+
@Setup(Level.Trial)
95+
public void setup() {
96+
engine = new MockParserEngineBuilder().build();
97+
content = generateContentWithPlaceholders(placeholderCount);
98+
}
99+
}
100+
101+
@Benchmark
102+
public Component benchmarkScalingWithPlaceholders(PlaceholderState state) {
103+
final MockComponentBuilder b = state.engine.parse(state.content);
104+
105+
for (int i = 0; i < state.placeholderCount; i++) {
106+
b.with("placeholder_" + i, "<color:#" + String.format("%06x", i * 1000) + ">Value " + i + "</color>");
107+
}
108+
109+
return b.build();
110+
}
111+
112+
private static String generateContentWithPlaceholders(int count) {
113+
final StringBuilder sb = new StringBuilder("<gradient:red:blue>");
114+
for (int i = 0; i < count; i++) {
115+
sb.append("<placeholder_").append(i).append("> ");
116+
}
117+
sb.append("</gradient>");
118+
return sb.toString();
119+
}
120+
121+
@SuppressWarnings("SameParameterValue")
122+
private static String generateContentWithTags(int count) {
123+
final StringBuilder sb = new StringBuilder();
124+
for (int i = 0; i < count; i++) {
125+
sb.append("<tag_").append(i).append("> ");
126+
}
127+
return sb.toString();
128+
}
129+
}

gradle/libs.versions.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[versions]
22
adventure = "4.21.0"
33
adventure-platform = "4.4.0"
4+
jmh = "1.37"
45

56
[libraries]
67
annotations = "org.jetbrains:annotations:26.0.2"
@@ -34,11 +35,18 @@ junit-bom = "org.junit:junit-bom:5.13.1"
3435
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter" }
3536
junit-platform = { module = "org.junit.platform:junit-platform-launcher" }
3637

38+
# Benchmarking
39+
jmh-core = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" }
40+
jmh-generator = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" }
41+
jmh-bytecode = { module = "org.openjdk.jmh:jmh-generator-bytecode", version.ref = "jmh" }
42+
3743
[bundles]
3844
junit = ["junit-jupiter", "junit-platform"]
45+
jmh = ["jmh-core", "jmh-generator", "jmh-bytecode"]
3946
adventure = ["adventure-api", "adventure-minimessage", "adventure-gson", "adventure-legacy", "adventure-plain"]
4047

4148
[plugins]
49+
jmh = "me.champeau.jmh:0.7.3"
4250
publisher = "com.vanniktech.maven.publish:0.32.0"
4351
shadow = "com.gradleup.shadow:8.3.6"
4452
run-paper = "xyz.jpenilla.run-paper:2.3.1"

0 commit comments

Comments
 (0)