From d185072ee022d61c660dff5d96413bf721075f8e Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Fri, 15 Mar 2024 20:38:58 +0700 Subject: [PATCH] Added new metric 'roompicker_locked_rooms' --- .../picker/aspect/UserMetricsAspect.java | 79 +++++++++++++------ .../dragonestia/picker/config/TestConfig.java | 6 +- .../picker/controller/RoomController.java | 4 +- .../event/UpdateRoomLockStateEvent.java | 11 +++ .../ru/dragonestia/picker/model/Room.java | 9 ++- .../picker/model/factory/RoomFactory.java | 22 ++++++ .../picker/config/FillingNodesConfig.java | 6 +- .../picker/service/RoomServiceTests.java | 26 +++--- 8 files changed, 125 insertions(+), 38 deletions(-) create mode 100644 server/src/main/java/ru/dragonestia/picker/event/UpdateRoomLockStateEvent.java create mode 100644 server/src/main/java/ru/dragonestia/picker/model/factory/RoomFactory.java diff --git a/server/src/main/java/ru/dragonestia/picker/aspect/UserMetricsAspect.java b/server/src/main/java/ru/dragonestia/picker/aspect/UserMetricsAspect.java index da2e9ae..7ba64e3 100644 --- a/server/src/main/java/ru/dragonestia/picker/aspect/UserMetricsAspect.java +++ b/server/src/main/java/ru/dragonestia/picker/aspect/UserMetricsAspect.java @@ -9,12 +9,17 @@ import lombok.extern.log4j.Log4j2; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; +import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; +import ru.dragonestia.picker.event.UpdateRoomLockStateEvent; import ru.dragonestia.picker.model.Node; +import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.repository.UserRepository; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -28,64 +33,94 @@ public class UserMetricsAspect { private final MeterRegistry meterRegistry; private final AtomicInteger totalUsers = new AtomicInteger(0); - private final Map nodeGauges = new ConcurrentHashMap<>(); - private final Map nodeUsers = new ConcurrentHashMap<>(); - private final Map pickPerMinute = new ConcurrentHashMap<>(); + private final Map data = new ConcurrentHashMap<>(); @PostConstruct void init() { meterRegistry.gauge("roompicker_total_users", totalUsers); } - @After("execution(* ru.dragonestia.picker.repository.UserRepository.linkWithRoom(..))") - void onLinkUsers() { - countAllUsers(); + @After(value = "execution(* ru.dragonestia.picker.repository.UserRepository.linkWithRoom(ru.dragonestia.picker.model.Room, ..)) && args(room, ..)", argNames = "room") + void onLinkUsers(Room room) { + countAllUsers(room); } - @After("execution(void ru.dragonestia.picker.repository.UserRepository.unlinkWithRoom(..))") - void onUnlinkUsers() { - countAllUsers(); + @After(value = "execution(void ru.dragonestia.picker.repository.UserRepository.unlinkWithRoom(ru.dragonestia.picker.model.Room, ..)) && args(room, ..)", argNames = "room") + void onUnlinkUsers(Room room) { + countAllUsers(room); } - @After("execution(void ru.dragonestia.picker.repository.UserRepository.onRemoveRoom(..))") - void onRemoveRoom() { - countAllUsers(); + @AfterReturning(value = "execution(void ru.dragonestia.picker.repository.RoomRepository.create(ru.dragonestia.picker.model.Room)) && args(room)", argNames = "room") + void onCreateRoom(Room room) { + checkRoom(room); } - private void countAllUsers() { + @After(value = "execution(void ru.dragonestia.picker.repository.UserRepository.onRemoveRoom(ru.dragonestia.picker.model.Room)) && args(room, ..)", argNames = "room") + void onRemoveRoom(Room room) { + countAllUsers(room); + } + + private void countAllUsers(Room room) { totalUsers.set(userRepository.countAllUsers()); + + checkRoom(room); + } + + private void checkRoom(Room room) { + var set = data.get(room.getNodeIdentifier()).locked(); + if (room.isLocked()) { + set.add(room); + return; + } + if (!room.hasUnlimitedSlots() && userRepository.usersOf(room).size() >= room.getMaxSlots()) { + set.add(room); + return; + } + set.remove(room); } @After(value = "execution(void ru.dragonestia.picker.repository.NodeRepository.create(ru.dragonestia.picker.model.Node)) && args(node)", argNames = "node") void onCreateNode(Node node) { var nodeId = node.getIdentifier(); - var gauge = Gauge.builder("roompicker_node_users_total", () -> nodeUsers.getOrDefault(nodeId, 0)) + var gauge = Gauge.builder("roompicker_node_users_total", () -> data.get(nodeId).users()) .tag("nodeId", nodeId) .register(meterRegistry); - nodeGauges.put(nodeId, gauge); - var counter = Counter.builder("roompicker_picks") .tag("nodeId", nodeId) .baseUnit("1s") .register(meterRegistry); - pickPerMinute.put(nodeId, counter); + + var lockedGauge = Gauge.builder("roompicker_locked_rooms", () -> data.get(nodeId).locked().size()) + .tag("nodeId", nodeId) + .register(meterRegistry); + + data.put(nodeId, new NodeData(gauge, new AtomicInteger(0), counter, new HashSet<>(), lockedGauge)); } @After(value = "execution(* ru.dragonestia.picker.repository.NodeRepository.delete(ru.dragonestia.picker.model.Node)) && args(node)", argNames = "node") void onDeleteNode(Node node) { - meterRegistry.remove(nodeGauges.remove(node.getIdentifier())); - meterRegistry.remove(pickPerMinute.remove(node.getIdentifier())); - nodeUsers.remove(node.getIdentifier()); + var data = this.data.remove(node.getIdentifier()); + + meterRegistry.remove(data.usersGauge()); + meterRegistry.remove(data.picksPerMinute()); + meterRegistry.remove(data.lockedGauge()); } @AfterReturning(value = "execution(* ru.dragonestia.picker.repository.RoomRepository.pickFree(ru.dragonestia.picker.model.Node, *)) && args(node, ..)", argNames = "node") void onPickRoom(Node node) { - pickPerMinute.get(node.getIdentifier()).increment(); + data.get(node.getIdentifier()).picksPerMinute().increment(); + } + + @EventListener + void onRoomChangeLockState(UpdateRoomLockStateEvent event) { + checkRoom(event.room()); } @Scheduled(fixedDelay = 3_000) void updateUserMetrics() { - nodeUsers.putAll(userRepository.countUsersForNodes()); + userRepository.countUsersForNodes().forEach((nodeId, users) -> data.get(nodeId).users().set(users)); } + + private record NodeData(Gauge usersGauge, AtomicInteger users, Counter picksPerMinute, Set locked, Gauge lockedGauge) {} } diff --git a/server/src/main/java/ru/dragonestia/picker/config/TestConfig.java b/server/src/main/java/ru/dragonestia/picker/config/TestConfig.java index b6af4be..7a52b0d 100644 --- a/server/src/main/java/ru/dragonestia/picker/config/TestConfig.java +++ b/server/src/main/java/ru/dragonestia/picker/config/TestConfig.java @@ -19,6 +19,7 @@ import ru.dragonestia.picker.interceptor.DebugInterceptor; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.model.User; +import ru.dragonestia.picker.model.factory.RoomFactory; import ru.dragonestia.picker.model.type.SlotLimit; import ru.dragonestia.picker.repository.RoomRepository; import ru.dragonestia.picker.repository.NodeRepository; @@ -36,6 +37,7 @@ public class TestConfig implements WebMvcConfigurer { private final NodeRepository nodeRepository; private final RoomRepository roomRepository; private final UserRepository userRepository; + private final RoomFactory roomFactory; private final Random rand = new Random(0); @@ -58,7 +60,7 @@ public class TestConfig implements WebMvcConfigurer { for (int i = 1; i <= 5; i++) { var slots = 5 * i; - var room = new Room(RoomIdentifier.of("test-" + i), node, slots, json.writeValueAsString(generatePayload()), false); + var room = roomFactory.create(RoomIdentifier.of("test-" + i), node, slots, json.writeValueAsString(generatePayload()), false); roomRepository.create(room); for (int j = 0, n = rand.nextInt(slots + 1); j < n; j++) { @@ -68,7 +70,7 @@ public class TestConfig implements WebMvcConfigurer { } for (int i = 0; i < 5; i++) { - var room = new Room(RoomIdentifier.of(randomUUID().toString()), node, IRoom.UNLIMITED_SLOTS, json.writeValueAsString(generatePayload()), false); + var room = roomFactory.create(RoomIdentifier.of(randomUUID().toString()), node, IRoom.UNLIMITED_SLOTS, json.writeValueAsString(generatePayload()), false); room.setLocked((i & 1) == 0); roomRepository.create(room); } diff --git a/server/src/main/java/ru/dragonestia/picker/controller/RoomController.java b/server/src/main/java/ru/dragonestia/picker/controller/RoomController.java index 5aecb1a..2c1a578 100644 --- a/server/src/main/java/ru/dragonestia/picker/controller/RoomController.java +++ b/server/src/main/java/ru/dragonestia/picker/controller/RoomController.java @@ -13,6 +13,7 @@ import ru.dragonestia.picker.api.repository.response.RoomInfoResponse; import ru.dragonestia.picker.api.repository.response.RoomListResponse; import ru.dragonestia.picker.api.repository.type.RoomIdentifier; import ru.dragonestia.picker.model.Room; +import ru.dragonestia.picker.model.factory.RoomFactory; import ru.dragonestia.picker.service.RoomService; import ru.dragonestia.picker.service.NodeService; import ru.dragonestia.picker.util.DetailsParser; @@ -28,6 +29,7 @@ public class RoomController { private final RoomService roomService; private final NamingValidator namingValidator; private final DetailsParser detailsParser; + private final RoomFactory roomFactory; @Operation(summary = "Get all rooms from node") @GetMapping @@ -54,7 +56,7 @@ public class RoomController { @Parameter(description = "Save room") @RequestParam(name = "persist", required = false, defaultValue = "false") boolean persist ) { var node = nodeService.find(nodeId).orElseThrow(() -> new NodeNotFoundException(nodeId)); - var room = new Room(RoomIdentifier.of(roomId), node, slots, payload, persist); + var room = roomFactory.create(RoomIdentifier.of(roomId), node, slots, payload, persist); room.setLocked(locked); roomService.create(room); diff --git a/server/src/main/java/ru/dragonestia/picker/event/UpdateRoomLockStateEvent.java b/server/src/main/java/ru/dragonestia/picker/event/UpdateRoomLockStateEvent.java new file mode 100644 index 0000000..afb453a --- /dev/null +++ b/server/src/main/java/ru/dragonestia/picker/event/UpdateRoomLockStateEvent.java @@ -0,0 +1,11 @@ +package ru.dragonestia.picker.event; + +import org.jetbrains.annotations.NotNull; +import ru.dragonestia.picker.model.Room; + +import java.util.function.Consumer; + +public record UpdateRoomLockStateEvent(@NotNull Room room) { + + public interface Listener extends Consumer {} +} diff --git a/server/src/main/java/ru/dragonestia/picker/model/Room.java b/server/src/main/java/ru/dragonestia/picker/model/Room.java index ff21dca..fb3bfa5 100644 --- a/server/src/main/java/ru/dragonestia/picker/model/Room.java +++ b/server/src/main/java/ru/dragonestia/picker/model/Room.java @@ -7,6 +7,7 @@ import ru.dragonestia.picker.api.model.room.ResponseRoom; import ru.dragonestia.picker.api.model.room.RoomDetails; import ru.dragonestia.picker.api.model.room.ShortResponseRoom; import ru.dragonestia.picker.api.repository.type.RoomIdentifier; +import ru.dragonestia.picker.event.UpdateRoomLockStateEvent; import java.util.Objects; @@ -18,13 +19,15 @@ public class Room implements IRoom { private final String payload; private final boolean persist; private boolean locked = false; + private final UpdateRoomLockStateEvent.Listener updateLockStateListener; - public Room(@NotNull RoomIdentifier identifier, @NotNull Node node, int slots, @NotNull String payload, boolean persist) { + public Room(@NotNull RoomIdentifier identifier, @NotNull Node node, int slots, @NotNull String payload, boolean persist, @Nullable UpdateRoomLockStateEvent.Listener listener) { this.identifier = identifier.getValue(); this.nodeIdentifier = node.getIdentifier(); this.slots = slots; this.payload = payload; this.persist = persist; + this.updateLockStateListener = listener; } @Override @@ -49,6 +52,10 @@ public class Room implements IRoom { public void setLocked(boolean value) { locked = value; + + if (updateLockStateListener != null) { + updateLockStateListener.accept(new UpdateRoomLockStateEvent(this)); + } } @Override diff --git a/server/src/main/java/ru/dragonestia/picker/model/factory/RoomFactory.java b/server/src/main/java/ru/dragonestia/picker/model/factory/RoomFactory.java new file mode 100644 index 0000000..938e6c2 --- /dev/null +++ b/server/src/main/java/ru/dragonestia/picker/model/factory/RoomFactory.java @@ -0,0 +1,22 @@ +package ru.dragonestia.picker.model.factory; + +import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Component; +import ru.dragonestia.picker.api.repository.type.RoomIdentifier; +import ru.dragonestia.picker.model.Node; +import ru.dragonestia.picker.model.Room; + +@Component +@RequiredArgsConstructor +public class RoomFactory { + + private final ApplicationEventPublisher eventPublisher; + + @Contract("_, _, _, _, _ -> new") + public @NotNull Room create(@NotNull RoomIdentifier identifier, @NotNull Node node, int slots, @NotNull String payload, boolean persist) { + return new Room(identifier, node, slots, payload, persist, eventPublisher::publishEvent); + } +} diff --git a/server/src/test/java/ru/dragonestia/picker/config/FillingNodesConfig.java b/server/src/test/java/ru/dragonestia/picker/config/FillingNodesConfig.java index 07faf52..d55f491 100644 --- a/server/src/test/java/ru/dragonestia/picker/config/FillingNodesConfig.java +++ b/server/src/test/java/ru/dragonestia/picker/config/FillingNodesConfig.java @@ -10,6 +10,7 @@ import ru.dragonestia.picker.api.repository.type.UserIdentifier; import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.User; +import ru.dragonestia.picker.model.factory.RoomFactory; import ru.dragonestia.picker.repository.NodeRepository; import ru.dragonestia.picker.repository.RoomRepository; import ru.dragonestia.picker.repository.UserRepository; @@ -47,6 +48,9 @@ public class FillingNodesConfig { @Autowired private UserRepository userRepository; + @Autowired + private RoomFactory roomFactory; + private Node seqNode; private Node roundNode; private Node leastNode; @@ -85,7 +89,7 @@ public class FillingNodesConfig { for (int i = 0, n = 5; i < n; i++) { for (int j = 0; j < 3; j++) { var roomId = "room-" + i + "-" + j; - var room = new Room(RoomIdentifier.of(roomId), node, n, "", false); + var room = roomFactory.create(RoomIdentifier.of(roomId), node, n, "", false); roomRepository.create(room); var users = n - i; diff --git a/server/src/test/java/ru/dragonestia/picker/service/RoomServiceTests.java b/server/src/test/java/ru/dragonestia/picker/service/RoomServiceTests.java index 80b9ad3..304e87a 100644 --- a/server/src/test/java/ru/dragonestia/picker/service/RoomServiceTests.java +++ b/server/src/test/java/ru/dragonestia/picker/service/RoomServiceTests.java @@ -17,6 +17,7 @@ import ru.dragonestia.picker.api.repository.type.UserIdentifier; import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.User; +import ru.dragonestia.picker.model.factory.RoomFactory; import ru.dragonestia.picker.model.type.SlotLimit; import java.util.List; @@ -30,6 +31,9 @@ public class RoomServiceTests { @Autowired private RoomService roomService; + @Autowired + private RoomFactory roomFactory; + private Node node; @BeforeEach @@ -43,7 +47,7 @@ public class RoomServiceTests { @Test void test_createAndRemove() { - var room = new Room(RoomIdentifier.of("test-room"), node, IRoom.UNLIMITED_SLOTS, "", false); + var room = roomFactory.create(RoomIdentifier.of("test-room"), node, IRoom.UNLIMITED_SLOTS, "", false); roomService.create(room); Assertions.assertTrue(roomService.find(node, room.getIdentifier()).isPresent()); @@ -57,10 +61,10 @@ public class RoomServiceTests { @Test void test_allRooms() { var rooms = List.of( - new Room(RoomIdentifier.of("test-room1"), node, 1, "", false), - new Room(RoomIdentifier.of("test-room2"), node, 2, "", false), - new Room(RoomIdentifier.of("test-room3"), node, 3, "", false), - new Room(RoomIdentifier.of("test-room4"), node, IRoom.UNLIMITED_SLOTS, "", false) + roomFactory.create(RoomIdentifier.of("test-room1"), node, 1, "", false), + roomFactory.create(RoomIdentifier.of("test-room2"), node, 2, "", false), + roomFactory.create(RoomIdentifier.of("test-room3"), node, 3, "", false), + roomFactory.create(RoomIdentifier.of("test-room4"), node, IRoom.UNLIMITED_SLOTS, "", false) ); rooms.forEach(room -> roomService.create(room)); @@ -74,17 +78,17 @@ public class RoomServiceTests { @Test void test_exceptNotPersistedNode() { Assertions.assertThrows(NotPersistedNodeException.class, () -> { - roomService.create(new Room(RoomIdentifier.of("1"), node, IRoom.UNLIMITED_SLOTS, "", true)); + roomService.create(roomFactory.create(RoomIdentifier.of("1"), node, IRoom.UNLIMITED_SLOTS, "", true)); }); } @Test void test_pickRoom() { var rooms = List.of( - new Room(RoomIdentifier.of("test-room1"), node, 1, "", false), - new Room(RoomIdentifier.of("test-room2"), node, 2, "", false), - new Room(RoomIdentifier.of("test-room3"), node, 3, "", false), - new Room(RoomIdentifier.of("test-room4"), node, IRoom.UNLIMITED_SLOTS, "", false) + roomFactory.create(RoomIdentifier.of("test-room1"), node, 1, "", false), + roomFactory.create(RoomIdentifier.of("test-room2"), node, 2, "", false), + roomFactory.create(RoomIdentifier.of("test-room3"), node, 3, "", false), + roomFactory.create(RoomIdentifier.of("test-room4"), node, IRoom.UNLIMITED_SLOTS, "", false) ); rooms.forEach(room -> roomService.create(room)); @@ -112,7 +116,7 @@ public class RoomServiceTests { @Test void test_nodeDoesNotExists() { var node = new Node(NodeIdentifier.of("bruh"), PickingMethod.ROUND_ROBIN, false); - var room = new Room(RoomIdentifier.of("test"), node, IRoom.UNLIMITED_SLOTS, "", false); + var room = roomFactory.create(RoomIdentifier.of("test"), node, IRoom.UNLIMITED_SLOTS, "", false); Assertions.assertThrows(NodeNotFoundException.class, () -> roomService.create(room)); Assertions.assertThrows(NodeNotFoundException.class, () -> roomService.remove(room));