Added new metric 'roompicker_locked_rooms'

This commit is contained in:
Andrey Terentev 2024-03-15 20:38:58 +07:00 committed by Andrey Terentev
parent cf76f2f8ce
commit d185072ee0
8 changed files with 125 additions and 38 deletions

View File

@ -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<String, Gauge> nodeGauges = new ConcurrentHashMap<>();
private final Map<String, Integer> nodeUsers = new ConcurrentHashMap<>();
private final Map<String, Counter> pickPerMinute = new ConcurrentHashMap<>();
private final Map<String, NodeData> 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<Room> locked, Gauge lockedGauge) {}
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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<UpdateRoomLockStateEvent> {}
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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));