Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored repositories #29

Merged
merged 13 commits into from
Mar 16, 2024
Merged
Binary file added .github/img/grafana.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,13 @@ class Example {
}
}
}
```
```


Stress testing
----------

If you want to stress test the server, you can run the script from the `noiser` module.
Before starting stress testing, you'd better run Prometheus and Grafana from `docker-compose.yml`.
In Grafana, you can see this view:
![Grafana](.github/img/grafana.png)
10 changes: 5 additions & 5 deletions RoomPicker-Grafana-Dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@
"axisPlacement": "left",
"barAlignment": 0,
"drawStyle": "bars",
"fillOpacity": 30,
"fillOpacity": 26,
"gradientMode": "none",
"hideFrom": {
"legend": false,
Expand All @@ -462,7 +462,7 @@
},
"insertNulls": false,
"lineInterpolation": "stepAfter",
"lineWidth": 2,
"lineWidth": 3,
"pointSize": 3,
"scaleDistribution": {
"type": "linear"
Expand All @@ -471,7 +471,7 @@
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
Expand Down Expand Up @@ -725,13 +725,13 @@
"list": []
},
"time": {
"from": "now-6h",
"from": "now-15m",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "RoomPicker",
"uid": "e3009deb-a342-4a32-9d13-3b44ed3461bc",
"version": 24,
"version": 26,
"weekStart": ""
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ public void saveNode(@NotNull NodeDefinition definition) {

@Override
public @NotNull PickedRoomResponse pickRoom(@NotNull NodeIdentifier identifier, @NotNull Set<UserIdentifier> users) {
throw new UnsupportedOperationException("Not implemented");
return rest.queryPostWithBody(
"/nodes/" + identifier.getValue() + "/pick",
PickedRoomResponse.class,
params -> {}, String.join(",", users.stream().map(user -> user.getValue()).toList())
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Response;
import okhttp3.*;
import org.jetbrains.annotations.ApiStatus.Internal;
import ru.dragonestia.picker.api.exception.ExceptionFactory;
import ru.dragonestia.picker.api.impl.RoomPickerClient;
Expand Down Expand Up @@ -78,6 +76,22 @@ public <T> T query(String uri, HttpMethod method, Class<T> clazz, ParamsConsumer
}
}

public <T> T queryPostWithBody(String uri, Class<T> clazz, ParamsConsumer paramsConsumer, String body) {
var request = client.prepareRequestBuilder(uri + queryEncode(paramsConsumer))
.post(RequestBody.create(body, MediaType.get("text/plain")))
.build();

try (var response = httpClient.newCall(request).execute()) {
checkResponseForErrors(response);

return json.readValue(new String(Objects.requireNonNull(response.body()).bytes(), StandardCharsets.UTF_8), clazz);
} catch (JsonProcessingException ex) {
throw new RuntimeException("Json processing error", ex);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}

private String queryEncode(ParamsConsumer paramsConsumer) {
var params = new HashMap<String, String>();
paramsConsumer.accept(params);
Expand Down
32 changes: 32 additions & 0 deletions control-panel/frontend/generated/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/******************************************************************************
* This file is auto-generated by Vaadin.
* If you want to customize the entry point, you can copy this file or create
* your own `index.ts` in your frontend directory.
* By default, the `index.ts` file should be in `./frontend/` folder.
*
* NOTE:
* - You need to restart the dev-server after adding the new `index.ts` file.
* After that, all modifications to `index.ts` are recompiled automatically.
* - `index.js` is also supported if you don't want to use TypeScript.
******************************************************************************/

// import Vaadin client-router to handle client-side and server-side navigation
import { Router } from '@vaadin/router';

// import Flow module to enable navigation to Vaadin server-side views
import { Flow } from 'Frontend/generated/jar-resources/Flow.js';

const { serverSideRoutes } = new Flow({
imports: () => import('Frontend/generated/flow/generated-flow-imports.js')
});

const routes = [
// for client-side, place routes below (more info https://hilla.dev/docs/lit/guides/routing#initializing-the-router)

// for server-side, the next magic line sends all unmatched routes:
...serverSideRoutes // IMPORTANT: this must be the last entry in the array
];

// Vaadin router needs an outlet in the index.html page to display views
const router = new Router(document.querySelector('#outlet'));
router.setRoutes(routes);
22 changes: 22 additions & 0 deletions noiser/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
plugins {
id 'java'
}

group = 'ru.dragonestia'
version = 'unspecified'

repositories {
mavenCentral()
}

dependencies {
implementation project(":client-api")
implementation project(":client-impl")

testImplementation platform('org.junit:junit-bom:5.9.1')
testImplementation 'org.junit.jupiter:junit-jupiter'
}

test {
useJUnitPlatform()
}
127 changes: 127 additions & 0 deletions noiser/src/main/java/ru/dragonestia/picker/noiser/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package ru.dragonestia.picker.noiser;

import ru.dragonestia.picker.api.impl.RoomPickerClient;
import ru.dragonestia.picker.api.model.node.NodeDefinition;
import ru.dragonestia.picker.api.model.node.PickingMethod;
import ru.dragonestia.picker.api.model.room.RoomDefinition;
import ru.dragonestia.picker.api.repository.query.node.GetAllNodes;
import ru.dragonestia.picker.api.repository.query.user.UnlinkUsersFromRoom;
import ru.dragonestia.picker.api.repository.type.NodeIdentifier;
import ru.dragonestia.picker.api.repository.type.RoomIdentifier;
import ru.dragonestia.picker.api.repository.type.UserIdentifier;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;


public class Main {

private final ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(8);
private final Random random = new Random();
private final RoomPickerClient client;
private final List<NodeIdentifier> nodes;

private final Map<NodeIdentifier, AtomicInteger> totalUsers = new ConcurrentHashMap<>();
private final int expectedUsers = 10000;

public Main() {
client = new RoomPickerClient("http://localhost:8080", "admin", "qwerty123");
nodes = initNodes();
}

private void removeAll() {
client.getNodeRepository().allNodes(GetAllNodes.JUST)
.forEach(node -> client.getNodeRepository().removeNode(node));
}

private List<NodeIdentifier> initNodes() {
removeAll();

var list = new ArrayList<NodeIdentifier>();

for (int i = 0; i < 5; i++) {
var node = new NodeDefinition(NodeIdentifier.of("test-node-" + i))
.setPickingMethod(PickingMethod.values()[i % PickingMethod.values().length]);

client.getNodeRepository().saveNode(node);

var nodeId = node.getIdentifierObject();
totalUsers.put(nodeId, new AtomicInteger(0));
list.add(nodeId);
}

return list;
}

private void initRooms() {
final int perNode = expectedUsers / nodes.size();
final int roomsPerNode = perNode / 10;

for (var nodeId: nodes) {
for (int i = 0; i < roomsPerNode; i++) {
client.getRoomRepository().saveRoom(
new RoomDefinition(nodeId, RoomIdentifier.of(UUID.randomUUID().toString())).setMaxSlots(10)
);
}
}
}

private void pickUsers() {
for (var nodeId: nodes) {
var usersInNode = totalUsers.get(nodeId);

try {
synchronized (usersInNode) {
var users = new HashSet<UserIdentifier>();
var maxAdd = Math.min(10, (expectedUsers / nodes.size()) - usersInNode.get());

if (maxAdd == 0) return;
var add = maxAdd == 1 ? 1 : (random.nextInt(maxAdd - 1) + 1);
for (int i = 0; i < add; i++) {
users.add(UserIdentifier.of(UUID.randomUUID().toString()));
}

var request = client.getNodeRepository().pickRoom(nodeId, users);
usersInNode.addAndGet(add);
var roomId = RoomIdentifier.of(request.roomId());

System.out.printf("Added %s(total %s) users to %s/%s%n", add, usersInNode.get(), nodeId.getValue(), roomId.getValue());

scheduler.schedule(() -> {
try {
client.getUserRepository().unlinkUsersFromRoom(UnlinkUsersFromRoom.builder()
.setNodeId(nodeId)
.setRoomId(roomId)
.setUsers(users)
.build());

usersInNode.addAndGet(-add);
System.out.printf("Reduced %s users from %s/%s%n", add, nodeId.getValue(), roomId.getValue());
} catch (Exception ex) {
System.out.println("Error(" + ex.getClass().getSimpleName() + "): " + ex.getMessage());
}
}, random.nextInt(10) + 1, TimeUnit.SECONDS);
}
} catch (Exception ex) {
System.out.println("Error(" + ex.getClass().getSimpleName() + "): " + ex.getMessage());
}
}
}

public void startNoise() throws InterruptedException {
initRooms();

while (true) {
pickUsers();
Thread.sleep(10);
}
}

public static void main(String[] args) throws InterruptedException {
new Main().startNoise();
}
}
Loading
Loading