From 2240b7094ee202088ed7052421416b835cfa78eb Mon Sep 17 00:00:00 2001 From: Rsl1122 <24460436+Rsl1122@users.noreply.github.com> Date: Sat, 2 Jul 2022 20:05:00 +0300 Subject: [PATCH] Wrote a performance test generation utility Not yet finished, does not yet generate all necessary data for tests. It would be a good idea to store this data in SQLite files and then import those to MySQL if necessary when running performance tests. Idea is to allow generating multiple different scenarios and then measure the performance of different endpoints to estimate time complexity and find bottlenecks. --- .../PerformanceTestDataGenerator.java | 122 ++++++++++++++++++ .../src/test/java/utilities/RandomData.java | 44 ++++++- .../utilities/mocks/PluginMockComponent.java | 5 +- 3 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 Plan/common/src/test/java/utilities/PerformanceTestDataGenerator.java diff --git a/Plan/common/src/test/java/utilities/PerformanceTestDataGenerator.java b/Plan/common/src/test/java/utilities/PerformanceTestDataGenerator.java new file mode 100644 index 000000000..56d14c6c9 --- /dev/null +++ b/Plan/common/src/test/java/utilities/PerformanceTestDataGenerator.java @@ -0,0 +1,122 @@ +package utilities; + +import com.djrapitops.plan.gathering.domain.BaseUser; +import com.djrapitops.plan.gathering.domain.FinishedSession; +import com.djrapitops.plan.identification.Server; +import com.djrapitops.plan.identification.ServerUUID; +import com.djrapitops.plan.storage.database.DaggerDatabaseTestComponent; +import com.djrapitops.plan.storage.database.Database; +import com.djrapitops.plan.storage.database.DatabaseTestComponent; +import com.djrapitops.plan.storage.database.transactions.StoreServerInformationTransaction; +import com.djrapitops.plan.storage.database.transactions.events.PlayerServerRegisterTransaction; +import com.djrapitops.plan.storage.database.transactions.events.StoreJoinAddressTransaction; +import com.djrapitops.plan.storage.database.transactions.events.StoreSessionTransaction; +import com.djrapitops.plan.storage.database.transactions.events.WorldNameStoreTransaction; +import net.playeranalytics.plugin.scheduling.TimeAmount; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class PerformanceTestDataGenerator { + + public static Path tempDir; + private static Database database; + + public static void main(String[] args) throws Exception { + tempDir = createTempDir(); + + DatabaseTestComponent component = DaggerDatabaseTestComponent.builder() + .bindTemporaryDirectory(tempDir) + .build(); + + DBPreparer dbPreparer = new DBPreparer(component, RandomData.randomNonPrivilegedPort()); + database = dbPreparer.prepareMySQL() + .orElseThrow(() -> new IllegalStateException("Could not initialize database")); + + int numberOfServers = 1; + int worldsPerServer = 1; + int numberOfJoinAddresses = 50; + int numberOfPlayers = 1000; + int sessionsPerDay = 100; + + long timespan = TimeAmount.MONTH.toMillis(2); + long earliestDate = System.currentTimeMillis() - timespan; + + long numberOfSessions = sessionsPerDay * TimeUnit.MILLISECONDS.toDays(timespan); + long numberOfTPSEntries = TimeUnit.MILLISECONDS.toMinutes(timespan); + long numberOfPingEntries = TimeUnit.MILLISECONDS.toMinutes(timespan) * sessionsPerDay / 20; + + List serverUUIDs = RandomData.pickMultiple(numberOfServers, ServerUUID::randomUUID); + Map allWorlds = new HashMap<>(); + for (ServerUUID serverUUID : serverUUIDs) { + Server server = RandomData.randomServer(serverUUID); + database.executeTransaction(new StoreServerInformationTransaction(server)); + + List worlds = RandomData.pickMultiple(worldsPerServer, () -> RandomData.randomString(30)); + allWorlds.put(serverUUID, worlds.toArray(new String[0])); + for (String world : worlds) { + database.executeTransaction(new WorldNameStoreTransaction(serverUUID, world)); + } + } + + List joinAddresses = RandomData.pickMultiple(numberOfJoinAddresses, () -> RandomData.randomString(255)); + for (String joinAddress : joinAddresses) { + database.executeTransaction(new StoreJoinAddressTransaction(joinAddress)); + } + + + List players = RandomData.pickMultiple(numberOfPlayers, () -> RandomData.randomBaseUser(earliestDate)); + Map playerLookup = players.stream() + .collect(Collectors.toMap(BaseUser::getUuid, Function.identity())); + + List sessions = RandomData.pickMultiple(numberOfSessions, () -> { + ServerUUID server = RandomData.pickAtRandom(serverUUIDs); + UUID[] playerUUIDs = RandomData.pickMultiple(RandomData.randomInt(1, 8), () -> RandomData.pickAtRandom(players)) + .stream() + .map(BaseUser::getUuid) + .distinct() + .toArray(UUID[]::new); + + return RandomData.randomSession( + server, + allWorlds.get(server), + playerUUIDs + ); + }); + + Map> playersToRegister = new HashMap<>(); + for (FinishedSession session : sessions) { + UUID playerUUID = session.getPlayerUUID(); + ServerUUID serverUUID = session.getServerUUID(); + + Set usersOfServer = playersToRegister.computeIfAbsent(serverUUID, k -> new HashSet<>()); + usersOfServer.add(playerLookup.get(playerUUID)); + } + + for (var usersOfServer : playersToRegister.entrySet()) { + ServerUUID serverUUID = usersOfServer.getKey(); + for (BaseUser baseUser : usersOfServer.getValue()) { + database.executeTransaction(new PlayerServerRegisterTransaction( + baseUser.getUuid(), + baseUser::getRegistered, + baseUser.getName(), + serverUUID, + () -> RandomData.pickAtRandom(joinAddresses))); + } + } + + for (FinishedSession session : sessions) { + database.executeTransaction(new StoreSessionTransaction(session)); + } + } + + private static Path createTempDir() throws IOException { + return Files.createTempDirectory("plan-performance-test-data-generator"); + } + +} diff --git a/Plan/common/src/test/java/utilities/RandomData.java b/Plan/common/src/test/java/utilities/RandomData.java index d7847926b..14afd3ee5 100644 --- a/Plan/common/src/test/java/utilities/RandomData.java +++ b/Plan/common/src/test/java/utilities/RandomData.java @@ -21,6 +21,7 @@ import com.djrapitops.plan.delivery.domain.Nickname; import com.djrapitops.plan.delivery.rendering.json.graphs.line.Point; import com.djrapitops.plan.gathering.domain.*; import com.djrapitops.plan.gathering.domain.event.JoinAddress; +import com.djrapitops.plan.identification.Server; import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.storage.database.sql.tables.KillsTable; import org.apache.commons.lang3.RandomStringUtils; @@ -47,6 +48,11 @@ public class RandomData { return ThreadLocalRandom.current().nextInt(rangeStart, rangeEnd); } + public static int randomNonPrivilegedPort() { + return ThreadLocalRandom.current() + .nextInt(65535 - 1024) + 1024; + } + public static long randomTime() { return randomTimeAfter(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(60L)); } @@ -99,7 +105,11 @@ public class RandomData { return from[randomInt(0, from.length)]; } - public static List pickMultiple(int howMany, Supplier supplier) { + public static T pickAtRandom(List from) { + return from.get(randomInt(0, from.size())); + } + + public static List pickMultiple(long howMany, Supplier supplier) { List picked = new ArrayList<>(); for (int i = 0; i < howMany; i++) { picked.add(supplier.get()); @@ -112,9 +122,13 @@ public class RandomData { } public static FinishedSession randomSession(ServerUUID serverUUID, String[] worlds, UUID... uuids) { + return randomSession(RandomData.randomTime(), serverUUID, worlds, uuids); + } + + public static FinishedSession randomSession(long after, ServerUUID serverUUID, String[] worlds, UUID... uuids) { DataMap extraData = new DataMap(); extraData.put(WorldTimes.class, RandomData.randomWorldTimes(worlds)); - long start = RandomData.randomTime(); + long start = RandomData.randomTimeAfter(after); long end = RandomData.randomTimeAfter(start); if (uuids.length >= 2) { @@ -210,4 +224,30 @@ public class RandomData { .map(JoinAddress::new) .collect(Collectors.toList()); } + + public static Server randomServer(ServerUUID serverUUID) { + return new Server( + serverUUID, + RandomData.randomString(16), + "http://localhost", + RandomData.randomVersion() + ); + } + + private static String randomVersion() { + return RandomData.randomInt(1, 20) + + "." + + RandomData.randomInt(0, 200) + + " build " + + RandomData.randomInt(0, 100000); + } + + public static BaseUser randomBaseUser(long earliestDate) { + return new BaseUser( + UUID.randomUUID(), + RandomData.randomString(20), + RandomData.randomLong(earliestDate, System.currentTimeMillis()), + RandomData.randomInt(0, 100) + ); + } } diff --git a/Plan/common/src/test/java/utilities/mocks/PluginMockComponent.java b/Plan/common/src/test/java/utilities/mocks/PluginMockComponent.java index ebff0087f..fefe998ff 100644 --- a/Plan/common/src/test/java/utilities/mocks/PluginMockComponent.java +++ b/Plan/common/src/test/java/utilities/mocks/PluginMockComponent.java @@ -22,11 +22,11 @@ import com.djrapitops.plan.settings.config.paths.WebserverSettings; import com.djrapitops.plan.storage.database.SQLDB; import com.djrapitops.plan.utilities.logging.PluginErrorLogger; import net.playeranalytics.plugin.PlatformAbstractionLayer; +import utilities.RandomData; import utilities.dagger.DaggerPlanPluginComponent; import utilities.dagger.PlanPluginComponent; import java.nio.file.Path; -import java.util.concurrent.ThreadLocalRandom; /** * Test utility for creating a dagger PlanComponent using a mocked Plan. @@ -58,8 +58,7 @@ public class PluginMockComponent { public PlanSystem getPlanSystem() throws Exception { initComponent(); PlanSystem system = component.system(); - system.getConfigSystem().getConfig().set(WebserverSettings.PORT, ThreadLocalRandom.current() - .nextInt(65535 - 1024) + 1024); // Random non-privileged port + system.getConfigSystem().getConfig().set(WebserverSettings.PORT, RandomData.randomNonPrivilegedPort()); return system; }