+5
-2
README.md
+5
-2
README.md
···
1
1
# minecraft-stress-test
2
2
3
-
Automate the stress testing of your 1.19.2 Minecraft server with bots.
3
+
Automate the stress testing of your 1.19.3 Minecraft server with bots.
4
4
This project will log offline-mode bots into the specified server which will
5
5
fly around and explore the world.
6
6
···
55
55
Set the bot count to 1000. This will either connect more bots, or disconnect existing bots as needed.
56
56
57
57
`speed 0.2`
58
-
Set the bots' movement speed. The default is 0.2 blocks/tick.
58
+
Set the bots' movement speed. The default is 0.1 blocks/tick.
59
+
60
+
`radius 600`
61
+
Set the radius of the area the bots can move around in. The default is 1000 blocks.
+54
-60
src/main/java/com/github/puregero/minecraftstresstest/Bot.java
+54
-60
src/main/java/com/github/puregero/minecraftstresstest/Bot.java
···
9
9
import java.util.concurrent.CompletableFuture;
10
10
import java.util.concurrent.Executor;
11
11
import java.util.concurrent.TimeUnit;
12
+
import java.util.function.Consumer;
12
13
13
14
public class Bot extends ChannelInboundHandlerAdapter {
14
-
private static final int PROTOCOL_VERSION = Integer.parseInt(System.getProperty("bot.protocol.version", "760")); // 760 is 1.19.2 https://wiki.vg/Protocol_version_numbers
15
-
private static final double RADIUS = Double.parseDouble(System.getProperty("bot.radius", "1000"));
15
+
private static final int PROTOCOL_VERSION = Integer.parseInt(System.getProperty("bot.protocol.version", "761")); // 761 is 1.19.3 https://wiki.vg/Protocol_version_numbers
16
16
private static final double CENTER_X = Double.parseDouble(System.getProperty("bot.x", "0"));
17
17
private static final double CENTER_Z = Double.parseDouble(System.getProperty("bot.z", "0"));
18
18
private static final boolean LOGS = Boolean.parseBoolean(System.getProperty("bot.logs", "true"));
···
22
22
23
23
private static final Executor ONE_TICK_DELAY = CompletableFuture.delayedExecutor(50,TimeUnit.MILLISECONDS);
24
24
25
-
public static double SPEED = Double.parseDouble(System.getProperty("bot.speed", "0.1"));
25
+
public static final String DEFAULT_SPEED = "0.1";
26
+
public static double SPEED = Double.parseDouble(System.getProperty("bot.speed", DEFAULT_SPEED));
27
+
public static final String DEFAULT_RADIUS = "1000";
28
+
public static double RADIUS = Double.parseDouble(System.getProperty("bot.radius", DEFAULT_RADIUS));
26
29
27
30
public SocketChannel channel;
28
31
private String username;
···
47
50
48
51
@Override
49
52
public void channelActive(final ChannelHandlerContext ctx) {
50
-
FriendlyByteBuf handshakePacket = new FriendlyByteBuf(ctx.alloc().buffer());
51
-
handshakePacket.writeVarInt(0x00);
52
-
handshakePacket.writeVarInt(PROTOCOL_VERSION);
53
-
handshakePacket.writeUtf(address);
54
-
handshakePacket.writeShort(port);
55
-
handshakePacket.writeVarInt(2);
56
-
ctx.write(handshakePacket);
53
+
sendPacket(ctx, PacketIds.Serverbound.Handshaking.HANDSHAKE, buffer -> {
54
+
buffer.writeVarInt(PROTOCOL_VERSION);
55
+
buffer.writeUtf(address);
56
+
buffer.writeShort(port);
57
+
buffer.writeVarInt(2);
58
+
});
57
59
58
-
FriendlyByteBuf loginStartPacket = new FriendlyByteBuf(ctx.alloc().buffer());
59
-
loginStartPacket.writeVarInt(0x00);
60
-
loginStartPacket.writeUtf(username);
61
-
loginStartPacket.writeBoolean(false);
62
-
loginStartPacket.writeBoolean(false);
63
-
ctx.writeAndFlush(loginStartPacket);
60
+
sendPacket(ctx, PacketIds.Serverbound.Login.LOGIN_START, buffer -> {
61
+
buffer.writeUtf(username);
62
+
buffer.writeBoolean(false);
63
+
});
64
64
}
65
65
66
66
@Override
···
87
87
private void channelReadLogin(ChannelHandlerContext ctx, FriendlyByteBuf byteBuf) {
88
88
int packetId = byteBuf.readVarInt();
89
89
90
-
if (packetId == 0) {
90
+
if (packetId == PacketIds.Clientbound.Login.DISCONNECT) {
91
91
System.out.println(username + " was disconnected during login due to " + byteBuf.readUtf());
92
92
ctx.close();
93
-
} else if (packetId == 2) {
93
+
} else if (packetId == PacketIds.Clientbound.Login.LOGIN_SUCCESS) {
94
94
UUID uuid = byteBuf.readUUID();
95
95
String username = byteBuf.readUtf();
96
96
loggedIn(ctx, uuid, username);
97
-
} else if (packetId == 3) {
97
+
} else if (packetId == PacketIds.Clientbound.Login.SET_COMPRESSION) {
98
98
byteBuf.readVarInt();
99
99
ctx.pipeline().addAfter("packetDecoder", "compressionDecoder", new CompressionDecoder());
100
100
ctx.pipeline().addAfter("packetEncoder", "compressionEncoder", new CompressionEncoder());
···
110
110
loginState = false;
111
111
112
112
CompletableFuture.delayedExecutor(1000,TimeUnit.MILLISECONDS).execute(() -> {
113
-
FriendlyByteBuf settingsPacket = new FriendlyByteBuf(ctx.alloc().buffer());
114
-
settingsPacket.writeVarInt(0x08);
115
-
settingsPacket.writeUtf("en_GB");
116
-
settingsPacket.writeByte(VIEW_DISTANCE);
117
-
settingsPacket.writeVarInt(0);
118
-
settingsPacket.writeBoolean(true);
119
-
settingsPacket.writeByte(0);
120
-
settingsPacket.writeVarInt(0);
121
-
settingsPacket.writeBoolean(false);
122
-
settingsPacket.writeBoolean(true);
123
-
ctx.writeAndFlush(settingsPacket);
113
+
sendPacket(ctx, PacketIds.Serverbound.Play.CLIENT_INFORMATION, buffer -> {
114
+
buffer.writeUtf("en_GB");
115
+
buffer.writeByte(VIEW_DISTANCE);
116
+
buffer.writeVarInt(0);
117
+
buffer.writeBoolean(true);
118
+
buffer.writeByte(0);
119
+
buffer.writeVarInt(0);
120
+
buffer.writeBoolean(false);
121
+
buffer.writeBoolean(true);
122
+
});
124
123
125
124
CompletableFuture.delayedExecutor(1000, TimeUnit.MILLISECONDS).execute(() -> tick(ctx));
126
125
});
···
166
165
y -= SPEED / 10;
167
166
}
168
167
169
-
FriendlyByteBuf movePacket = new FriendlyByteBuf(ctx.alloc().buffer());
170
-
movePacket.writeVarInt(0x15);
171
-
movePacket.writeDouble(x);
172
-
movePacket.writeDouble(y);
173
-
movePacket.writeDouble(z);
174
-
movePacket.writeFloat(yaw);
175
-
movePacket.writeFloat(0);
176
-
movePacket.writeBoolean(true);
177
-
ctx.writeAndFlush(movePacket);
168
+
sendPacket(ctx, PacketIds.Serverbound.Play.SET_PLAYER_POSITION_AND_ROTATION, buffer -> {
169
+
buffer.writeDouble(x);
170
+
buffer.writeDouble(y);
171
+
buffer.writeDouble(z);
172
+
buffer.writeFloat(yaw);
173
+
buffer.writeFloat(0);
174
+
buffer.writeBoolean(true);
175
+
});
178
176
}
179
177
180
178
private void channelReadPlay(ChannelHandlerContext ctx, FriendlyByteBuf byteBuf) {
181
179
int packetId = byteBuf.readVarInt();
182
180
// System.out.println("id 0x" + Integer.toHexString(packetId) + " (" + (dataLength == 0 ? length : dataLength) + ")");
183
181
184
-
if (packetId == 0x19) {
182
+
if (packetId == PacketIds.Clientbound.Play.DISCONNECT) {
185
183
System.out.println(username + " (" + uuid + ") was kicked due to " + byteBuf.readUtf());
186
184
ctx.close();
187
-
} else if (packetId == 0x20) {
185
+
} else if (packetId == PacketIds.Clientbound.Play.KEEP_ALIVE) {
188
186
long id = byteBuf.readLong();
189
187
190
-
FriendlyByteBuf keepAlivePacket = new FriendlyByteBuf(ctx.alloc().buffer());
191
-
keepAlivePacket.writeVarInt(0x12);
192
-
keepAlivePacket.writeLong(id);
193
-
ctx.writeAndFlush(keepAlivePacket);
194
-
} else if (packetId == 0x2F) {
188
+
sendPacket(ctx, PacketIds.Serverbound.Play.KEEP_ALIVE, buffer -> buffer.writeLong(id));
189
+
} else if (packetId == PacketIds.Clientbound.Play.PING) {
195
190
int id = byteBuf.readInt();
196
191
197
-
FriendlyByteBuf keepAlivePacket = new FriendlyByteBuf(ctx.alloc().buffer());
198
-
keepAlivePacket.writeVarInt(0x20);
199
-
keepAlivePacket.writeInt(id);
200
-
ctx.writeAndFlush(keepAlivePacket);
201
-
} else if (packetId == 0x39) {
192
+
sendPacket(ctx, PacketIds.Serverbound.Play.PONG, buffer -> buffer.writeInt(id));
193
+
} else if (packetId == PacketIds.Clientbound.Play.SYNCHRONIZE_PLAYER_POSITION) {
202
194
double dx = byteBuf.readDouble();
203
195
double dy = byteBuf.readDouble();
204
196
double dz = byteBuf.readDouble();
···
227
219
if (!goDown) yaw = (float) (Math.random() * 360);
228
220
}
229
221
230
-
FriendlyByteBuf teleportConfirmPacket = new FriendlyByteBuf(ctx.alloc().buffer());
231
-
teleportConfirmPacket.writeVarInt(0x00);
232
-
teleportConfirmPacket.writeVarInt(id);
233
-
ctx.writeAndFlush(teleportConfirmPacket);
234
-
} else if (packetId == 0x3D) {
222
+
sendPacket(ctx, PacketIds.Serverbound.Play.CONFIRM_TELEPORTATION, buffer -> buffer.writeVarInt(id));
223
+
} else if (packetId == PacketIds.Clientbound.Play.RESOURCE_PACK) {
235
224
String url = byteBuf.readUtf();
236
225
String hash = byteBuf.readUtf();
237
226
boolean forced = byteBuf.readBoolean();
238
227
String message = null;
239
228
if (byteBuf.readBoolean()) message = byteBuf.readUtf();
240
229
System.out.println("Resource pack info:\n" + url + "\n" + hash + "\n" + forced + "\n" + message);
241
-
FriendlyByteBuf resourcePackResponsePacket = new FriendlyByteBuf(ctx.alloc().buffer());
242
-
resourcePackResponsePacket.writeVarInt(0x24);
243
-
resourcePackResponsePacket.writeVarInt(RESOURCE_PACK_RESPONSE);
244
-
ctx.writeAndFlush(resourcePackResponsePacket);
230
+
231
+
sendPacket(ctx, PacketIds.Serverbound.Play.RESOURCE_PACK, buffer -> buffer.writeVarInt(RESOURCE_PACK_RESPONSE));
245
232
}
246
233
}
247
234
248
235
public void close() {
249
236
channel.close();
237
+
}
238
+
239
+
public static void sendPacket(ChannelHandlerContext ctx, int packetId, Consumer<FriendlyByteBuf> applyToBuffer) {
240
+
FriendlyByteBuf buffer = new FriendlyByteBuf(ctx.alloc().buffer());
241
+
buffer.writeVarInt(packetId);
242
+
applyToBuffer.accept(buffer);
243
+
ctx.writeAndFlush(buffer);
250
244
}
251
245
}
+60
-17
src/main/java/com/github/puregero/minecraftstresstest/MinecraftStressTest.java
+60
-17
src/main/java/com/github/puregero/minecraftstresstest/MinecraftStressTest.java
···
11
11
import java.util.ArrayList;
12
12
import java.util.List;
13
13
import java.util.Scanner;
14
+
import java.util.concurrent.CompletableFuture;
15
+
import java.util.concurrent.TimeUnit;
16
+
import java.util.concurrent.atomic.AtomicBoolean;
17
+
import java.util.concurrent.locks.Lock;
18
+
import java.util.concurrent.locks.ReentrantLock;
14
19
15
20
public class MinecraftStressTest {
16
21
17
-
private static final int BOT_COUNT = Integer.parseInt(System.getProperty("bot.count", "1"));
18
22
private static final String ADDRESS = System.getProperty("bot.ip", "127.0.0.1");
19
23
private static final int PORT = Integer.parseInt(System.getProperty("bot.port", "25565"));
20
24
private static final int DELAY_BETWEEN_BOTS_MS = Integer.parseInt(System.getProperty("bot.login.delay.ms", "100"));
21
25
26
+
private static final String DEFAULT_BOT_COUNT = "1";
27
+
private static int BOT_COUNT = Integer.parseInt(System.getProperty("bot.count", DEFAULT_BOT_COUNT));
28
+
29
+
private static final List<Bot> bots = new ArrayList<>();
30
+
private static final Lock botsLock = new ReentrantLock();
31
+
private static final AtomicBoolean addingBots = new AtomicBoolean();
32
+
22
33
private static final EventLoopGroup workerGroup = new NioEventLoopGroup();
23
34
24
35
public static void main(String[] a) {
25
-
List<Bot> bots = new ArrayList<>();
26
-
27
-
updateBotCount(bots, BOT_COUNT);
36
+
updateBotCount();
28
37
29
38
Scanner scanner = new Scanner(System.in);
30
39
···
34
43
35
44
try {
36
45
if (args[0].equalsIgnoreCase("count") || args[0].equalsIgnoreCase("botcount")) {
37
-
int botCount = Integer.parseInt(args[1]);
46
+
int botCount = Math.max(0, Integer.parseInt(args[1]));
38
47
System.out.println("Setting bot count to " + botCount);
39
-
updateBotCount(bots, botCount);
48
+
BOT_COUNT = botCount;
49
+
updateBotCount();
40
50
} else if (args[0].equalsIgnoreCase("speed")) {
41
-
double speed = Double.parseDouble(args[1]);
51
+
double speed = Math.max(0.0, Double.parseDouble(args[1]));
42
52
System.out.println("Setting speed to " + speed);
43
53
Bot.SPEED = speed;
54
+
} else if (args[0].equalsIgnoreCase("radius")) {
55
+
double radius = Math.max(0.0, Double.parseDouble(args[1]));
56
+
System.out.println("Setting radius to " + radius);
57
+
Bot.RADIUS = radius;
44
58
} else {
45
59
System.out.println("Commands:");
46
-
System.out.println("count <number of bots>");
47
-
System.out.println("speed <0.2>");
60
+
System.out.println("count <number of bots> (Default: " + DEFAULT_BOT_COUNT + ")");
61
+
System.out.println("speed <value> (Default: " + Bot.DEFAULT_SPEED + ")");
62
+
System.out.println("radius <value> (Default: " + Bot.DEFAULT_RADIUS + ")");
48
63
}
49
64
} catch (Exception e) {
50
65
e.printStackTrace();
···
54
69
System.out.println("stdin ended");
55
70
}
56
71
57
-
private static void updateBotCount(List<Bot> bots, int botCount) {
58
-
while (bots.size() > botCount && !bots.isEmpty()) {
59
-
bots.remove(bots.size() - 1).close();
72
+
private static void updateBotCount() {
73
+
removeBotsIfNeeded();
74
+
addBotIfNeeded(true);
75
+
}
76
+
77
+
private static void removeBotsIfNeeded() {
78
+
botsLock.lock();
79
+
while (true) {
80
+
Bot removedBot;
81
+
try {
82
+
if (bots.size() <= BOT_COUNT) {
83
+
break;
84
+
}
85
+
removedBot = bots.remove(bots.size() - 1);
86
+
} finally {
87
+
botsLock.unlock();
88
+
}
89
+
removedBot.close();
60
90
}
91
+
}
61
92
62
-
while (bots.size() < botCount) {
63
-
bots.add(connectBot(System.getProperty("bot.name", "Bot") + (bots.size() + 1), ADDRESS, PORT));
93
+
private static void addBotIfNeeded(boolean firstCall) {
94
+
if (!firstCall || !addingBots.getAndSet(true)) {
95
+
boolean scheduledNextCall = false;
64
96
try {
65
-
Thread.sleep(DELAY_BETWEEN_BOTS_MS);
66
-
} catch (InterruptedException e) {
67
-
e.printStackTrace();
97
+
botsLock.lock();
98
+
try {
99
+
if (bots.size() < BOT_COUNT) {
100
+
bots.add(connectBot(System.getProperty("bot.name", "Bot") + (bots.size() + 1), ADDRESS, PORT));
101
+
CompletableFuture.delayedExecutor(DELAY_BETWEEN_BOTS_MS, TimeUnit.MILLISECONDS).execute(() -> addBotIfNeeded(false));
102
+
scheduledNextCall = true;
103
+
}
104
+
} finally {
105
+
botsLock.unlock();
106
+
}
107
+
} finally {
108
+
if (!scheduledNextCall) {
109
+
addingBots.set(false);
110
+
}
68
111
}
69
112
}
70
113
}
+57
src/main/java/com/github/puregero/minecraftstresstest/PacketIds.java
+57
src/main/java/com/github/puregero/minecraftstresstest/PacketIds.java
···
1
+
package com.github.puregero.minecraftstresstest;
2
+
3
+
public final class PacketIds {
4
+
private PacketIds() {}
5
+
6
+
public static final class Clientbound {
7
+
private Clientbound() {}
8
+
9
+
public static final class Login {
10
+
private Login() {}
11
+
public static final int
12
+
DISCONNECT = 0x00,
13
+
LOGIN_SUCCESS = 0x02,
14
+
SET_COMPRESSION = 0x03;
15
+
}
16
+
17
+
public static final class Play {
18
+
private Play() {}
19
+
public static final int
20
+
DISCONNECT = 0x17,
21
+
KEEP_ALIVE = 0x1F,
22
+
PING = 0x2E,
23
+
SYNCHRONIZE_PLAYER_POSITION = 0x38,
24
+
RESOURCE_PACK = 0x3C;
25
+
}
26
+
27
+
}
28
+
29
+
public static final class Serverbound {
30
+
private Serverbound() {}
31
+
32
+
public static final class Handshaking {
33
+
private Handshaking() {}
34
+
public static final int
35
+
HANDSHAKE = 0x00;
36
+
}
37
+
38
+
public static final class Login {
39
+
private Login() {}
40
+
public static final int
41
+
LOGIN_START = 0x00;
42
+
}
43
+
44
+
public static final class Play {
45
+
private Play() {}
46
+
public static final int
47
+
CONFIRM_TELEPORTATION = 0x00,
48
+
CLIENT_INFORMATION = 0x07,
49
+
KEEP_ALIVE = 0x11,
50
+
SET_PLAYER_POSITION_AND_ROTATION = 0x14,
51
+
PONG = 0x1F,
52
+
RESOURCE_PACK = 0x24;
53
+
}
54
+
55
+
}
56
+
57
+
}