Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d2cd3254b3 | |||
| 763a2b8c39 | |||
| 0eb752d493 | |||
| e279c39246 | |||
| a101866bcf | |||
| 5baf6a7cdb |
@@ -351,7 +351,10 @@
|
|||||||
- function unsubscribe
|
- function unsubscribe
|
||||||
- _...1 more_
|
- _...1 more_
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/redis/C2sMessage.scala` — class C2sMessage
|
- `modules/core/src/main/scala/de/nowchess/chess/redis/C2sMessage.scala` — class C2sMessage
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/redis/GameRedisPublisher.scala` — class GameRedisPublisher, function onGameEvent
|
- `modules/core/src/main/scala/de/nowchess/chess/redis/GameRedisPublisher.scala`
|
||||||
|
- class GameRedisPublisher
|
||||||
|
- class GameRedisPublisher
|
||||||
|
- function onGameEvent
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/redis/GameRedisSubscriberManager.scala`
|
- `modules/core/src/main/scala/de/nowchess/chess/redis/GameRedisSubscriberManager.scala`
|
||||||
- class GameRedisSubscriberManager
|
- class GameRedisSubscriberManager
|
||||||
- function subscribeGame
|
- function subscribeGame
|
||||||
@@ -657,7 +660,7 @@
|
|||||||
- `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` — imported by **9** files
|
- `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` — imported by **9** files
|
||||||
- `modules/account/src/main/scala/de/nowchess/account/config/RedisConfig.scala` — imported by **8** files
|
- `modules/account/src/main/scala/de/nowchess/account/config/RedisConfig.scala` — imported by **8** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/io/GameContextImport.scala` — imported by **8** files
|
- `modules/api/src/main/scala/de/nowchess/api/io/GameContextImport.scala` — imported by **8** files
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/grpc/IoGrpcClientWrapper.scala` — imported by **7** files
|
- `modules/core/src/main/scala/de/nowchess/chess/grpc/IoGrpcClientWrapper.scala` — imported by **8** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/player/PlayerInfo.scala` — imported by **6** files
|
- `modules/api/src/main/scala/de/nowchess/api/player/PlayerInfo.scala` — imported by **6** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameMode.scala` — imported by **6** files
|
- `modules/api/src/main/scala/de/nowchess/api/game/GameMode.scala` — imported by **6** files
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@
|
|||||||
- `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` — imported by **9** files
|
- `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` — imported by **9** files
|
||||||
- `modules/account/src/main/scala/de/nowchess/account/config/RedisConfig.scala` — imported by **8** files
|
- `modules/account/src/main/scala/de/nowchess/account/config/RedisConfig.scala` — imported by **8** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/io/GameContextImport.scala` — imported by **8** files
|
- `modules/api/src/main/scala/de/nowchess/api/io/GameContextImport.scala` — imported by **8** files
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/grpc/IoGrpcClientWrapper.scala` — imported by **7** files
|
- `modules/core/src/main/scala/de/nowchess/chess/grpc/IoGrpcClientWrapper.scala` — imported by **8** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/player/PlayerInfo.scala` — imported by **6** files
|
- `modules/api/src/main/scala/de/nowchess/api/player/PlayerInfo.scala` — imported by **6** files
|
||||||
- `modules/api/src/main/scala/de/nowchess/api/game/GameMode.scala` — imported by **6** files
|
- `modules/api/src/main/scala/de/nowchess/api/game/GameMode.scala` — imported by **6** files
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -295,7 +295,10 @@
|
|||||||
- function unsubscribe
|
- function unsubscribe
|
||||||
- _...1 more_
|
- _...1 more_
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/redis/C2sMessage.scala` — class C2sMessage
|
- `modules/core/src/main/scala/de/nowchess/chess/redis/C2sMessage.scala` — class C2sMessage
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/redis/GameRedisPublisher.scala` — class GameRedisPublisher, function onGameEvent
|
- `modules/core/src/main/scala/de/nowchess/chess/redis/GameRedisPublisher.scala`
|
||||||
|
- class GameRedisPublisher
|
||||||
|
- class GameRedisPublisher
|
||||||
|
- function onGameEvent
|
||||||
- `modules/core/src/main/scala/de/nowchess/chess/redis/GameRedisSubscriberManager.scala`
|
- `modules/core/src/main/scala/de/nowchess/chess/redis/GameRedisSubscriberManager.scala`
|
||||||
- class GameRedisSubscriberManager
|
- class GameRedisSubscriberManager
|
||||||
- function subscribeGame
|
- function subscribeGame
|
||||||
|
|||||||
+3
-1
@@ -3,6 +3,7 @@ plugins {
|
|||||||
id("org.scoverage") version "8.1" apply false
|
id("org.scoverage") version "8.1" apply false
|
||||||
id("com.diffplug.spotless") version "8.4.0" apply false
|
id("com.diffplug.spotless") version "8.4.0" apply false
|
||||||
id("io.github.cosmicsilence.scalafix") version "0.2.6" apply false
|
id("io.github.cosmicsilence.scalafix") version "0.2.6" apply false
|
||||||
|
id("me.champeau.jmh") version "0.7.2" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "de.nowchess"
|
group = "de.nowchess"
|
||||||
@@ -99,7 +100,8 @@ val versions = mapOf(
|
|||||||
"SCALA_PARSER_COMBINATORS" to "2.4.0",
|
"SCALA_PARSER_COMBINATORS" to "2.4.0",
|
||||||
"FASTPARSE" to "3.0.2",
|
"FASTPARSE" to "3.0.2",
|
||||||
"JACKSON" to "2.17.2",
|
"JACKSON" to "2.17.2",
|
||||||
"JACKSON_SCALA" to "2.17.2"
|
"JACKSON_SCALA" to "2.17.2",
|
||||||
|
"JMH" to "1.37"
|
||||||
)
|
)
|
||||||
extra["VERSIONS"] = versions
|
extra["VERSIONS"] = versions
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
# NowChess Load Testing with k6
|
||||||
|
|
||||||
|
Performance testing suite for NowChess services using k6.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install k6 (macOS)
|
||||||
|
brew install k6
|
||||||
|
|
||||||
|
# Or download from https://k6.io/docs/getting-started/installation/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Scenarios
|
||||||
|
|
||||||
|
### 1. Ramp-Up Test
|
||||||
|
Gradually increases load from 10 to 100 concurrent users.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
k6 run ramp-up.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Target: Identify system behavior under gradual load increase.
|
||||||
|
|
||||||
|
### 2. Stress Test
|
||||||
|
Incremental load increase up to 500 concurrent users to find breaking point.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
k6 run stress-test.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Target: Determine system capacity and failure point.
|
||||||
|
|
||||||
|
### 3. Spike Test
|
||||||
|
Sudden traffic surge (baseline 50 → 500 users instantly).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
k6 run spike-test.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Target: Test recovery and resilience to sudden spikes.
|
||||||
|
|
||||||
|
### 4. Constant Load Test
|
||||||
|
Maintains 50 VUs for 10 minutes.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
k6 run constant-load.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Target: Check stability under sustained load.
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Override service endpoints
|
||||||
|
export BASE_URL=http://localhost:8080
|
||||||
|
export ACCOUNT_HOST=http://localhost:8083
|
||||||
|
export STORE_HOST=http://localhost:8085
|
||||||
|
|
||||||
|
k6 run ramp-up.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Ensure services are running:
|
||||||
|
- Core: `localhost:8080`
|
||||||
|
- Account: `localhost:8083`
|
||||||
|
- Store: `localhost:8085`
|
||||||
|
- Redis: `localhost:6379` (with increased pool size)
|
||||||
|
|
||||||
|
## Metrics Interpretation
|
||||||
|
|
||||||
|
- `http_req_duration`: Response time (p95, p99 percentiles matter most)
|
||||||
|
- `http_req_failed`: Failed requests (connection errors, errors, non-2xx responses)
|
||||||
|
- `vus`: Virtual Users (concurrent connections)
|
||||||
|
- `iterations`: Completed test cycles per VU
|
||||||
|
|
||||||
|
## Results
|
||||||
|
|
||||||
|
k6 generates HTML report output. Use with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
k6 run --out=csv=results.csv ramp-up.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If you see:
|
||||||
|
- **"max pool wait timeout"** → Redis pool still too small or maxWaitTime too short
|
||||||
|
- **"connection refused"** → Service not running
|
||||||
|
- **"high p99 latency"** → System approaching capacity
|
||||||
|
|
||||||
|
Increase Redis pool settings in `modules/*/src/main/resources/application.yml`:
|
||||||
|
```yaml
|
||||||
|
quarkus:
|
||||||
|
redis:
|
||||||
|
pool:
|
||||||
|
max-size: 128 # Increase if still hitting limits
|
||||||
|
max-waiting: 256
|
||||||
|
```
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
export const BASE_URL = __ENV.BASE_URL || 'http://localhost:8080';
|
||||||
|
export const ACCOUNT_HOST = __ENV.ACCOUNT_HOST || 'http://localhost:8083';
|
||||||
|
export const STORE_HOST = __ENV.STORE_HOST || 'http://localhost:8085';
|
||||||
|
export const CORE_HOST = BASE_URL;
|
||||||
|
|
||||||
|
export const ENDPOINTS = {
|
||||||
|
// Account endpoints
|
||||||
|
accountCreateUser: `${ACCOUNT_HOST}/api/account`,
|
||||||
|
accountLogin: `${ACCOUNT_HOST}/api/account/login`,
|
||||||
|
accountProfile: `${ACCOUNT_HOST}/api/account/me`,
|
||||||
|
accountPublicProfile: (username) => `${ACCOUNT_HOST}/api/account/${username}`,
|
||||||
|
|
||||||
|
// Store endpoints
|
||||||
|
storeGame: `${STORE_HOST}/api/games`,
|
||||||
|
storeGameById: (gameId) => `${STORE_HOST}/api/games/${gameId}`,
|
||||||
|
|
||||||
|
// Core endpoints (game operations)
|
||||||
|
gameWebSocket: (gameId) => `ws://localhost:8080/api/games/${gameId}/ws`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TEST_USERS = [
|
||||||
|
{ username: 'load-test-user-1', email: 'load1@example.com' },
|
||||||
|
{ username: 'load-test-user-2', email: 'load2@example.com' },
|
||||||
|
{ username: 'load-test-user-3', email: 'load3@example.com' },
|
||||||
|
];
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import http from 'k6/http';
|
||||||
|
import { check, sleep } from 'k6';
|
||||||
|
import { ENDPOINTS } from './config.js';
|
||||||
|
|
||||||
|
export const options = {
|
||||||
|
vus: 50,
|
||||||
|
duration: '10m',
|
||||||
|
thresholds: {
|
||||||
|
http_req_duration: ['p(95)<500', 'p(99)<1000'],
|
||||||
|
http_req_failed: ['rate<0.1'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
// Simulate consistent user traffic
|
||||||
|
|
||||||
|
// 1. Get account profile
|
||||||
|
const profileRes = http.get(ENDPOINTS.accountProfile);
|
||||||
|
check(profileRes, {
|
||||||
|
'profile status is 200 or 401': (r) => r.status === 200 || r.status === 401,
|
||||||
|
});
|
||||||
|
|
||||||
|
sleep(0.5);
|
||||||
|
|
||||||
|
// 2. Get public profile
|
||||||
|
const publicRes = http.get(ENDPOINTS.accountPublicProfile('testuser'));
|
||||||
|
check(publicRes, {
|
||||||
|
'public profile status ok': (r) => r.status > 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
sleep(0.5);
|
||||||
|
|
||||||
|
// 3. List store games
|
||||||
|
const gamesRes = http.get(ENDPOINTS.storeGame);
|
||||||
|
check(gamesRes, {
|
||||||
|
'store games status ok': (r) => r.status > 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import http from 'k6/http';
|
||||||
|
import { check, sleep } from 'k6';
|
||||||
|
import { ENDPOINTS, ACCOUNT_HOST } from './config.js';
|
||||||
|
|
||||||
|
export const options = {
|
||||||
|
stages: [
|
||||||
|
{ duration: '1m', target: 10 },
|
||||||
|
{ duration: '3m', target: 50 },
|
||||||
|
{ duration: '5m', target: 100 },
|
||||||
|
{ duration: '3m', target: 50 },
|
||||||
|
{ duration: '1m', target: 0 },
|
||||||
|
],
|
||||||
|
thresholds: {
|
||||||
|
http_req_duration: ['p(95)<500', 'p(99)<1000'],
|
||||||
|
http_req_failed: ['rate<0.1'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
// Test account endpoints
|
||||||
|
const accountRes = http.get(ENDPOINTS.accountPublicProfile('testuser'));
|
||||||
|
check(accountRes, {
|
||||||
|
'account endpoint status is 200': (r) => r.status === 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
sleep(1);
|
||||||
|
|
||||||
|
// Test store endpoints
|
||||||
|
const storeRes = http.get(ENDPOINTS.storeGame);
|
||||||
|
check(storeRes, {
|
||||||
|
'store endpoint status is 200': (r) => r.status === 200 || r.status === 401,
|
||||||
|
});
|
||||||
|
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Default values
|
||||||
|
TEST_TYPE="${1:-ramp-up}"
|
||||||
|
BASE_URL="${BASE_URL:-http://localhost:8080}"
|
||||||
|
OUTPUT_FILE="${2:-results-$(date +%s).csv}"
|
||||||
|
|
||||||
|
echo -e "${BLUE}NowChess Load Test Runner${NC}"
|
||||||
|
echo "Test Type: $TEST_TYPE"
|
||||||
|
echo "Base URL: $BASE_URL"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if k6 is installed
|
||||||
|
if ! command -v k6 &> /dev/null; then
|
||||||
|
echo -e "${RED}Error: k6 not found. Install from https://k6.io/docs/getting-started/installation/${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$TEST_TYPE" in
|
||||||
|
ramp-up)
|
||||||
|
echo -e "${GREEN}Running Ramp-Up Test (10->100 VUs over 13 minutes)${NC}"
|
||||||
|
k6 run --out=csv=$OUTPUT_FILE ramp-up.js
|
||||||
|
;;
|
||||||
|
stress)
|
||||||
|
echo -e "${GREEN}Running Stress Test (up to 500 VUs)${NC}"
|
||||||
|
k6 run --out=csv=$OUTPUT_FILE stress-test.js
|
||||||
|
;;
|
||||||
|
spike)
|
||||||
|
echo -e "${GREEN}Running Spike Test (sudden 50->500 spike)${NC}"
|
||||||
|
k6 run --out=csv=$OUTPUT_FILE spike-test.js
|
||||||
|
;;
|
||||||
|
constant)
|
||||||
|
echo -e "${GREEN}Running Constant Load Test (50 VUs for 10m)${NC}"
|
||||||
|
k6 run --out=csv=$OUTPUT_FILE constant-load.js
|
||||||
|
;;
|
||||||
|
all)
|
||||||
|
echo -e "${GREEN}Running All Tests${NC}"
|
||||||
|
for test in ramp-up stress spike constant; do
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}Starting $test test...${NC}"
|
||||||
|
$0 $test
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${RED}Usage: $0 {ramp-up|stress|spike|constant|all} [output-file]${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 ramp-up"
|
||||||
|
echo " $0 stress results.csv"
|
||||||
|
echo " $0 all"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo -e "${GREEN}Test complete. Results saved to $OUTPUT_FILE${NC}"
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import http from 'k6/http';
|
||||||
|
import { check, sleep } from 'k6';
|
||||||
|
import { ENDPOINTS } from './config.js';
|
||||||
|
|
||||||
|
export const options = {
|
||||||
|
stages: [
|
||||||
|
{ duration: '1m', target: 50 },
|
||||||
|
{ duration: '30s', target: 500, ramp: 'fast' },
|
||||||
|
{ duration: '2m', target: 500 },
|
||||||
|
{ duration: '30s', target: 50, ramp: 'fast' },
|
||||||
|
{ duration: '1m', target: 0 },
|
||||||
|
],
|
||||||
|
thresholds: {
|
||||||
|
http_req_duration: ['p(95)<2000', 'p(99)<5000'],
|
||||||
|
http_req_failed: ['rate<0.3'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
// Rapid account checks
|
||||||
|
const urls = [
|
||||||
|
ENDPOINTS.accountPublicProfile('testuser'),
|
||||||
|
ENDPOINTS.storeGame,
|
||||||
|
ENDPOINTS.accountProfile,
|
||||||
|
];
|
||||||
|
|
||||||
|
urls.forEach((url) => {
|
||||||
|
const res = http.get(url);
|
||||||
|
check(res, {
|
||||||
|
'response received': (r) => r.status > 0,
|
||||||
|
'response time under 3s': (r) => r.timings.duration < 3000,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
sleep(0.1);
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import http from 'k6/http';
|
||||||
|
import { check, sleep } from 'k6';
|
||||||
|
import { ENDPOINTS } from './config.js';
|
||||||
|
|
||||||
|
export const options = {
|
||||||
|
stages: [
|
||||||
|
{ duration: '2m', target: 100 },
|
||||||
|
{ duration: '5m', target: 200 },
|
||||||
|
{ duration: '5m', target: 300 },
|
||||||
|
{ duration: '5m', target: 400 },
|
||||||
|
{ duration: '5m', target: 500 },
|
||||||
|
{ duration: '5m', target: 400 },
|
||||||
|
{ duration: '5m', target: 200 },
|
||||||
|
{ duration: '2m', target: 0 },
|
||||||
|
],
|
||||||
|
thresholds: {
|
||||||
|
http_req_duration: ['p(95)<1000', 'p(99)<2000'],
|
||||||
|
http_req_failed: ['rate<0.2'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const userIndex = Math.floor(Math.random() * 10);
|
||||||
|
|
||||||
|
// Burst account endpoint
|
||||||
|
const accountRes = http.get(ENDPOINTS.accountPublicProfile(`user-${userIndex}`));
|
||||||
|
check(accountRes, {
|
||||||
|
'account endpoint responds': (r) => r.status > 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Burst store endpoint
|
||||||
|
const storeRes = http.get(ENDPOINTS.storeGame);
|
||||||
|
check(storeRes, {
|
||||||
|
'store endpoint responds': (r) => r.status > 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
sleep(Math.random() * 0.5);
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package de.nowchess.account.client
|
package de.nowchess.account.client
|
||||||
|
|
||||||
import de.nowchess.security.InternalSecretClientFilter
|
import de.nowchess.security.{InternalClientHeadersFactory, InternalSecretClientFilter}
|
||||||
import jakarta.ws.rs.*
|
import jakarta.ws.rs.*
|
||||||
import jakarta.ws.rs.core.MediaType
|
import jakarta.ws.rs.core.MediaType
|
||||||
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider
|
import org.eclipse.microprofile.rest.client.annotation.{RegisterClientHeaders, RegisterProvider}
|
||||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient
|
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient
|
||||||
|
|
||||||
case class CorePlayerInfo(id: String, displayName: String)
|
case class CorePlayerInfo(id: String, displayName: String)
|
||||||
@@ -19,6 +19,7 @@ case class CoreGameResponse(gameId: String)
|
|||||||
@Path("/api/board/game")
|
@Path("/api/board/game")
|
||||||
@RegisterRestClient(configKey = "core-service")
|
@RegisterRestClient(configKey = "core-service")
|
||||||
@RegisterProvider(classOf[InternalSecretClientFilter])
|
@RegisterProvider(classOf[InternalSecretClientFilter])
|
||||||
|
@RegisterClientHeaders(classOf[InternalClientHeadersFactory])
|
||||||
trait CoreGameClient:
|
trait CoreGameClient:
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Gradle
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Eclipse
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.settings/
|
||||||
|
bin/
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
.idea
|
||||||
|
*.ipr
|
||||||
|
*.iml
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# NetBeans
|
||||||
|
nb-configuration.xml
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode
|
||||||
|
.factorypath
|
||||||
|
|
||||||
|
# OSX
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Vim
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# patch
|
||||||
|
*.orig
|
||||||
|
*.rej
|
||||||
|
|
||||||
|
# Local environment
|
||||||
|
.env
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
# JMH Benchmarks
|
||||||
|
|
||||||
|
Java Microbenchmark Harness (JMH) benchmarks for performance-critical components.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew modules:benchmarks:jmhJar
|
||||||
|
```
|
||||||
|
|
||||||
|
Produces `modules/benchmarks/build/libs/benchmarks-1.0-SNAPSHOT-jmh.jar`.
|
||||||
|
|
||||||
|
## Running Benchmarks
|
||||||
|
|
||||||
|
Since these benchmarks are written in Scala, JMH auto-discovery via jar packaging doesn't work. Instead, run benchmarks via classpath:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew modules:benchmarks:run --args='de.nowchess.benchmarks.MoveBenchmark'
|
||||||
|
```
|
||||||
|
|
||||||
|
Or compile and run with explicit classpath:
|
||||||
|
```bash
|
||||||
|
./gradlew modules:benchmarks:compileScala
|
||||||
|
java -cp "modules/benchmarks/build/classes/scala/main:modules/benchmarks/build/resources/main:$(./gradlew -q printClasspath)" \
|
||||||
|
org.openjdk.jmh.Main de.nowchess.benchmarks.MoveBenchmark
|
||||||
|
```
|
||||||
|
|
||||||
|
Adjust JMH options:
|
||||||
|
- `-f 1` — 1 fork (faster iteration, less reliable)
|
||||||
|
- `-i 3 -w 2` — 3 measurement iterations, 2 warmup iterations
|
||||||
|
- `-bm avgt` — average time mode
|
||||||
|
- `-tu us` — time unit (microseconds)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```bash
|
||||||
|
java -cp "..." org.openjdk.jmh.Main de.nowchess.benchmarks.MoveBenchmark -f 1 -i 3 -w 2 -bm avgt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Writing Benchmarks
|
||||||
|
|
||||||
|
Create Scala class with `@Benchmark` methods:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
@BenchmarkMode(Array(Mode.AverageTime, Mode.Throughput))
|
||||||
|
@OutputTimeUnit(TimeUnit.MICROSECONDS)
|
||||||
|
@State(Scope.Benchmark)
|
||||||
|
@Fork(2)
|
||||||
|
@Warmup(iterations = 3)
|
||||||
|
@Measurement(iterations = 5)
|
||||||
|
class SomeBenchmark {
|
||||||
|
@Setup(Level.Trial)
|
||||||
|
def setup(): Unit = ???
|
||||||
|
|
||||||
|
@org.openjdk.jmh.annotations.Benchmark
|
||||||
|
def benchmarkSomething(bh: Blackhole): Unit = {
|
||||||
|
val result = expensiveOperation()
|
||||||
|
bh.consume(result) // Prevent dead-code elimination
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benchmark Modes
|
||||||
|
|
||||||
|
- `AverageTime` — average execution time per operation
|
||||||
|
- `Throughput` — operations per unit time
|
||||||
|
- `SampleTime` — latency samples (min, max, percentiles)
|
||||||
|
- `SingleShotTime` — total time to execute N ops once
|
||||||
|
- `All` — run all modes
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
JMH's Gradle plugin doesn't fully support Scala annotation processing, so benchmarks must be run explicitly by class name rather than auto-discovered. This is a known limitation of JMH + Scala; consider Java-based benchmarks for full auto-discovery support.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [OpenJDK JMH](https://github.com/openjdk/jmh)
|
||||||
|
- [JMH Samples](https://hg.openjdk.org/code-tools/jmh/file/tip/jmh-samples)
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
plugins {
|
||||||
|
id("scala")
|
||||||
|
id("me.champeau.jmh")
|
||||||
|
}
|
||||||
|
|
||||||
|
group = "de.nowchess"
|
||||||
|
version = "1.0-SNAPSHOT"
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val versions = rootProject.extra["VERSIONS"] as Map<String, String>
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
scala {
|
||||||
|
scalaVersion = versions["SCALA3"]!!
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("org.scala-lang:scala3-library_3") {
|
||||||
|
version {
|
||||||
|
strictly(versions["SCALA3"]!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOnly("org.scala-lang:scala3-compiler_3") {
|
||||||
|
version {
|
||||||
|
strictly(versions["SCALA3"]!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Core modules for benchmarking
|
||||||
|
implementation(project(":modules:api"))
|
||||||
|
implementation(project(":modules:core"))
|
||||||
|
implementation(project(":modules:rule"))
|
||||||
|
implementation(project(":modules:bot-platform"))
|
||||||
|
|
||||||
|
// JMH (jmh configuration is for annotation processor + runtime)
|
||||||
|
implementation("org.openjdk.jmh:jmh-core:${versions["JMH"]!!}")
|
||||||
|
jmh("org.openjdk.jmh:jmh-core:${versions["JMH"]!!}")
|
||||||
|
jmh("org.openjdk.jmh:jmh-generator-annprocess:${versions["JMH"]!!}")
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations.matching { !it.name.startsWith("jmh") }.configureEach {
|
||||||
|
resolutionStrategy.force("org.scala-lang:scala-library:${versions["SCALA_LIBRARY"]!!}")
|
||||||
|
}
|
||||||
|
|
||||||
|
jmh {
|
||||||
|
jmhVersion.set(versions["JMH"]!!)
|
||||||
|
includeTests.set(false)
|
||||||
|
duplicateClassesStrategy.set(DuplicatesStrategy.WARN)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<JavaCompile> {
|
||||||
|
options.encoding = "UTF-8"
|
||||||
|
options.compilerArgs.add("-parameters")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<org.gradle.api.tasks.scala.ScalaCompile> {
|
||||||
|
scalaCompileOptions.additionalParameters = listOf("-encoding", "UTF-8")
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package de.nowchess.benchmarks
|
||||||
|
|
||||||
|
import org.openjdk.jmh.annotations.{BenchmarkMode, Fork, Measurement, OutputTimeUnit, Setup, State, Warmup}
|
||||||
|
import org.openjdk.jmh.annotations.Mode
|
||||||
|
import org.openjdk.jmh.annotations.Scope
|
||||||
|
import org.openjdk.jmh.annotations.Level
|
||||||
|
import org.openjdk.jmh.infra.Blackhole
|
||||||
|
import de.nowchess.api.board.{File, Rank, Square}
|
||||||
|
import de.nowchess.api.game.GameContext
|
||||||
|
import de.nowchess.rules.sets.DefaultRules
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
// scalafix:off DisableSyntax.var
|
||||||
|
@BenchmarkMode(Array(Mode.AverageTime, Mode.Throughput))
|
||||||
|
@OutputTimeUnit(TimeUnit.MICROSECONDS)
|
||||||
|
@State(Scope.Benchmark)
|
||||||
|
@Fork(value = 2, warmups = 1)
|
||||||
|
@Warmup(iterations = 3, time = 1)
|
||||||
|
@Measurement(iterations = 5, time = 1)
|
||||||
|
class MoveBenchmark {
|
||||||
|
private var gameContext: GameContext = scala.compiletime.uninitialized
|
||||||
|
|
||||||
|
@Setup(Level.Trial)
|
||||||
|
def setup(): Unit =
|
||||||
|
gameContext = GameContext.initial
|
||||||
|
|
||||||
|
@org.openjdk.jmh.annotations.Benchmark
|
||||||
|
def benchmarkAllLegalMoves(bh: Blackhole): Unit = {
|
||||||
|
val moves = DefaultRules.allLegalMoves(gameContext)
|
||||||
|
bh.consume(moves)
|
||||||
|
}
|
||||||
|
|
||||||
|
@org.openjdk.jmh.annotations.Benchmark
|
||||||
|
def benchmarkIsCheck(bh: Blackhole): Unit = {
|
||||||
|
val inCheck = DefaultRules.isCheck(gameContext)
|
||||||
|
bh.consume(inCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
@org.openjdk.jmh.annotations.Benchmark
|
||||||
|
def benchmarkLegalMovesFirstSquare(bh: Blackhole): Unit = {
|
||||||
|
val firstSquare = Square(File.A, Rank.R2)
|
||||||
|
val moves = DefaultRules.legalMoves(gameContext)(firstSquare)
|
||||||
|
bh.consume(moves)
|
||||||
|
}
|
||||||
|
|
||||||
|
@org.openjdk.jmh.annotations.Benchmark
|
||||||
|
def benchmarkIsCheckmate(bh: Blackhole): Unit = {
|
||||||
|
val result = DefaultRules.isCheckmate(gameContext)
|
||||||
|
bh.consume(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -118,3 +118,23 @@
|
|||||||
* **coordinator:** use genericKubernetesResources API for Argo Rollout scaling ([#44](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/44)) ([82d0b75](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/82d0b754be1075084944b466858672d944f9f7d8))
|
* **coordinator:** use genericKubernetesResources API for Argo Rollout scaling ([#44](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/44)) ([82d0b75](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/82d0b754be1075084944b466858672d944f9f7d8))
|
||||||
* **dependencies:** replace Fabric8 Kubernetes client with Quarkus Kubernetes client ([5f44570](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5f44570b357277d09f33b7296860c421e2e70ce0))
|
* **dependencies:** replace Fabric8 Kubernetes client with Quarkus Kubernetes client ([5f44570](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5f44570b357277d09f33b7296860c421e2e70ce0))
|
||||||
* **middleware:** update paths for bot generation and stockfish configuration ([2dd0501](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/2dd0501687db08dcd242359f6837125baf8a2fdc))
|
* **middleware:** update paths for bot generation and stockfish configuration ([2dd0501](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/2dd0501687db08dcd242359f6837125baf8a2fdc))
|
||||||
|
## (2026-05-05)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **config:** update application.yml for PostgreSQL and remove staging/production configurations ([2404e61](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/2404e6164c3b50ffccbea5238d636060d6abe4d6))
|
||||||
|
* **config:** update application.yml for staging and production environments ([6113432](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/6113432a14c476a3a0dfc0d449e17d023697f2ba))
|
||||||
|
* **docker:** add .dockerignore and .gitignore files for build exclusions ([c987d8e](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/c987d8e258c0e6c4cfbdaa8381c64c410d7a2b83))
|
||||||
|
* **docker:** add Dockerfiles for building Quarkus application in native and JVM modes ([3f2d2bb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/3f2d2bb4c97fa8cddba66e1da4427c54236dfeed))
|
||||||
|
* **docker:** add Dockerfiles for Quarkus application in JVM and native modes ([34b9933](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/34b993304670cf2aa62cd2f6460cee7b9864b08e))
|
||||||
|
* **logging:** add DEBUG/INFO/WARN logging across services (NCS-72) ([#41](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/41)) ([804a4bf](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/804a4bf179e3dfb19e2be4390e7e543caf5237c6))
|
||||||
|
* true-microservices ([#40](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/40)) ([5909242](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/590924254e8a2754de661a57a03e43f89ceb6299))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **coordinator:** refine type casting in rolloutSpec method ([#45](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/45)) ([d522f7f](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/d522f7f6edf9c985f03dd16816439d4184f1a589))
|
||||||
|
* **coordinator:** use genericKubernetesResources API for Argo Rollout scaling ([#43](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/43)) ([fa3c6b2](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fa3c6b2886dc59c14c5dad834acc9b41e42023bb))
|
||||||
|
* **coordinator:** use genericKubernetesResources API for Argo Rollout scaling ([#44](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/44)) ([82d0b75](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/82d0b754be1075084944b466858672d944f9f7d8))
|
||||||
|
* **dependencies:** replace Fabric8 Kubernetes client with Quarkus Kubernetes client ([5f44570](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5f44570b357277d09f33b7296860c421e2e70ce0))
|
||||||
|
* **middleware:** update paths for bot generation and stockfish configuration ([2dd0501](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/2dd0501687db08dcd242359f6837125baf8a2fdc))
|
||||||
|
* **redis:** update Redis configuration with max pool size and waiting parameters ([5baf6a7](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5baf6a7cdbea484fc49c02e2b5a1c3919b7fa2c4))
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ quarkus:
|
|||||||
port: 8086
|
port: 8086
|
||||||
redis:
|
redis:
|
||||||
hosts: redis://${REDIS_HOST:localhost}:${REDIS_PORT:6379}
|
hosts: redis://${REDIS_HOST:localhost}:${REDIS_PORT:6379}
|
||||||
|
max-pool-size: 64
|
||||||
|
max-pool-waiting: 128
|
||||||
grpc:
|
grpc:
|
||||||
server:
|
server:
|
||||||
port: 9086
|
port: 9086
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
MAJOR=0
|
MAJOR=0
|
||||||
MINOR=9
|
MINOR=10
|
||||||
PATCH=0
|
PATCH=0
|
||||||
|
|||||||
@@ -753,3 +753,47 @@
|
|||||||
* **redis:** simplify refreshRedisHeartbeat logic and ensure proper error handling ([1813ea1](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/1813ea1d2d5d093f7925f87371b5e29820bf1136))
|
* **redis:** simplify refreshRedisHeartbeat logic and ensure proper error handling ([1813ea1](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/1813ea1d2d5d093f7925f87371b5e29820bf1136))
|
||||||
* update main class path in build configuration and adjust VCS directory mapping ([7b1f8b1](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7b1f8b117623d327232a1a92a8a44d18582e0189))
|
* update main class path in build configuration and adjust VCS directory mapping ([7b1f8b1](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7b1f8b117623d327232a1a92a8a44d18582e0189))
|
||||||
* update move validation to check for king safety ([#13](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/13)) ([e5e20c5](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/e5e20c566e368b12ca1dc59680c34e9112bf6762))
|
* update move validation to check for king safety ([#13](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/13)) ([e5e20c5](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/e5e20c566e368b12ca1dc59680c34e9112bf6762))
|
||||||
|
## (2026-05-05)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add GameRules stub with PositionStatus enum ([76d4168](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/76d4168038de23e5d6083d4e8f0504fbf31d15a3))
|
||||||
|
* add MovedInCheck/Checkmate/Stalemate MoveResult variants (stub dispatch) ([8b7ec57](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/8b7ec57e5ea6ee1615a1883848a426dc07d26364))
|
||||||
|
* **config:** update application.yml for PostgreSQL and remove staging/production configurations ([2404e61](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/2404e6164c3b50ffccbea5238d636060d6abe4d6))
|
||||||
|
* **config:** update application.yml for staging and production environments ([6113432](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/6113432a14c476a3a0dfc0d449e17d023697f2ba))
|
||||||
|
* implement GameRules with isInCheck, legalMoves, gameStatus ([94a02ff](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/94a02ff6849436d9496c70a0f16c21666dae8e4e))
|
||||||
|
* implement legal castling ([#1](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/1)) ([00d326c](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/00d326c1ba67711fbe180f04e1100c3f01dd0254))
|
||||||
|
* **logging:** add DEBUG/INFO/WARN logging across services (NCS-72) ([#41](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/41)) ([804a4bf](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/804a4bf179e3dfb19e2be4390e7e543caf5237c6))
|
||||||
|
* NCS-10 Implement Pawn Promotion ([#12](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/12)) ([13bfc16](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/13bfc16cfe25db78ec607db523ca6d993c13430c))
|
||||||
|
* NCS-11 50-move rule ([#9](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/9)) ([412ed98](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/412ed986a95703a3b282276540153480ceed229d))
|
||||||
|
* NCS-13 Implement Threefold Repetition ([#31](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/31)) ([767d305](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/767d3051a76c266050b6335774d66e2db2273c16))
|
||||||
|
* NCS-14 implemented insufficient moves rule ([#30](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/30)) ([b0399a4](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b0399a4e489950083066c9538df9a84dcc7a4613))
|
||||||
|
* NCS-16 Core Separation via Patterns ([#10](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/10)) ([1361dfc](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/1361dfc89553b146864fb8ff3526cf12cf3f293a))
|
||||||
|
* NCS-17 Implement basic ScalaFX UI ([#14](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/14)) ([3ff8031](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/3ff80318b4f16c59733a46498581a5c27f048287))
|
||||||
|
* NCS-21 Write Scripts to automate certain tasks ([#15](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/15)) ([8051871](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/80518719d536a087d339fe02530825dc07f8b388))
|
||||||
|
* NCS-25 Add linters to keep quality up ([#27](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/27)) ([fd4e67d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/fd4e67d4f782a7e955822d90cb909d0a81676fb2))
|
||||||
|
* NCS-37 Quarkus integration ([#35](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/35)) ([f088c4e](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f088c4e9ffcc498d3d1b6f01e8f50042d5830d55))
|
||||||
|
* NCS-40 Rework Draw System ([#34](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/34)) ([33e785d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/33e785d22af87724839b62ae91dfe74a05b398c3))
|
||||||
|
* NCS-41 Bot Platform ([#33](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/33)) ([8744bee](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/8744bee2dd20966dae90a09c21a43d5b06f59e00))
|
||||||
|
* NCS-53 changed IO to MicroService for easier scaling ([#37](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/37)) ([b5a2966](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/b5a2966adafa9650f0f7d601bdeb8fdd13710327))
|
||||||
|
* NCS-6 Implementing FEN & PGN ([#7](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/7)) ([f28e69d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f28e69dc181416aa2f221fdc4b45c2cda5efbf07))
|
||||||
|
* NCS-9 En passant implementation ([#8](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/8)) ([919beb3](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/919beb3b4bfa8caf2f90976a415fe9b19b7e9747))
|
||||||
|
* **rule:** Rules as a microservice ([#39](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/39)) ([093134d](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/093134d36c6844ba02a36a28d5d044f09291cd1d))
|
||||||
|
* true-microservices ([#40](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/40)) ([5909242](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/590924254e8a2754de661a57a03e43f89ceb6299))
|
||||||
|
* wire check/checkmate/stalemate into processMove and gameLoop ([5264a22](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5264a225418b885c5e6ea6411b96f85e38837f6c))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add missing kings to gameLoop capture test board ([aedd787](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/aedd787b77203c2af934751dba7b784eaf165032))
|
||||||
|
* **auth:** change InternalAuthFilter to use @Singleton and add HTTP tests for secret validation ([c08d530](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/c08d5303eb9e70d36c8eebf6a061ccb71e118fe5))
|
||||||
|
* **auth:** update InternalAuthFilter to use @ApplicationScoped and add index-dependency configuration ([6e0fd95](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/6e0fd9523e001756ce7109e639ebb54be4fcdabf))
|
||||||
|
* correct test board positions and captureOutput/withInput interaction ([f0481e2](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/f0481e2561b779df00925b46ee281dc36a795150))
|
||||||
|
* **heartbeat:** inject ObjectMapper into InstanceHeartbeatService ([#42](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/42)) ([0c98151](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/0c981517da1f94cd10ae396e47bde2b35d0b3ba0))
|
||||||
|
* IO microservice ([#38](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/38)) ([a386f57](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/a386f57c21d34ead6cc6f92836c52b714597e289))
|
||||||
|
* Lints ([dc224ab](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/dc224abe26acf5361c56956006e1cc51b75b0b7e))
|
||||||
|
* **redis:** add max pool wait time and switch to ReactiveRedisDataSource for heartbeat updates ([33e5017](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/33e5017f51a998327b180f778f73964cc10c05d3))
|
||||||
|
* **redis:** prevent concurrent Redis heartbeat refreshes using AtomicBoolean ([847b132](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/847b13202cb909d18ca3304c27ebe17ce2312b8e))
|
||||||
|
* **redis:** simplify refreshRedisHeartbeat logic and ensure proper error handling ([1813ea1](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/1813ea1d2d5d093f7925f87371b5e29820bf1136))
|
||||||
|
* **redis:** update Redis configuration with max pool size and waiting parameters ([5baf6a7](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5baf6a7cdbea484fc49c02e2b5a1c3919b7fa2c4))
|
||||||
|
* update main class path in build configuration and adjust VCS directory mapping ([7b1f8b1](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/7b1f8b117623d327232a1a92a8a44d18582e0189))
|
||||||
|
* update move validation to check for king safety ([#13](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/13)) ([e5e20c5](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/e5e20c566e368b12ca1dc59680c34e9112bf6762))
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ quarkus:
|
|||||||
artifact-id: security
|
artifact-id: security
|
||||||
redis:
|
redis:
|
||||||
hosts: redis://${REDIS_HOST:localhost}:${REDIS_PORT:6379}
|
hosts: redis://${REDIS_HOST:localhost}:${REDIS_PORT:6379}
|
||||||
max-pool-wait-time: 500ms
|
max-pool-wait-time: 30s
|
||||||
|
max-pool-size: 64
|
||||||
|
max-pool-waiting: 128
|
||||||
grpc:
|
grpc:
|
||||||
clients:
|
clients:
|
||||||
rule-grpc:
|
rule-grpc:
|
||||||
|
|||||||
+31
-27
@@ -10,8 +10,9 @@ import de.nowchess.chess.observer.Observer
|
|||||||
import de.nowchess.chess.registry.GameRegistry
|
import de.nowchess.chess.registry.GameRegistry
|
||||||
import de.nowchess.chess.resource.GameDtoMapper
|
import de.nowchess.chess.resource.GameDtoMapper
|
||||||
import de.nowchess.chess.service.InstanceHeartbeatService
|
import de.nowchess.chess.service.InstanceHeartbeatService
|
||||||
|
import io.quarkus.redis.datasource.ReactiveRedisDataSource
|
||||||
import io.quarkus.redis.datasource.RedisDataSource
|
import io.quarkus.redis.datasource.RedisDataSource
|
||||||
import io.quarkus.redis.datasource.pubsub.PubSubCommands
|
import io.quarkus.redis.datasource.pubsub.ReactivePubSubCommands
|
||||||
import jakarta.annotation.PreDestroy
|
import jakarta.annotation.PreDestroy
|
||||||
import jakarta.enterprise.context.ApplicationScoped
|
import jakarta.enterprise.context.ApplicationScoped
|
||||||
import jakarta.enterprise.inject.Instance
|
import jakarta.enterprise.inject.Instance
|
||||||
@@ -29,6 +30,7 @@ class GameRedisSubscriberManager:
|
|||||||
|
|
||||||
// scalafix:off DisableSyntax.var
|
// scalafix:off DisableSyntax.var
|
||||||
@Inject var redis: RedisDataSource = uninitialized
|
@Inject var redis: RedisDataSource = uninitialized
|
||||||
|
@Inject var reactiveRedis: ReactiveRedisDataSource = uninitialized
|
||||||
@Inject var registry: GameRegistry = uninitialized
|
@Inject var registry: GameRegistry = uninitialized
|
||||||
@Inject var objectMapper: ObjectMapper = uninitialized
|
@Inject var objectMapper: ObjectMapper = uninitialized
|
||||||
@Inject var redisConfig: RedisConfig = uninitialized
|
@Inject var redisConfig: RedisConfig = uninitialized
|
||||||
@@ -40,7 +42,7 @@ class GameRedisSubscriberManager:
|
|||||||
if heartbeatServiceInstance.isUnsatisfied then None
|
if heartbeatServiceInstance.isUnsatisfied then None
|
||||||
else Some(heartbeatServiceInstance.get())
|
else Some(heartbeatServiceInstance.get())
|
||||||
|
|
||||||
private val c2sListeners = new ConcurrentHashMap[String, PubSubCommands.RedisSubscriber]()
|
private val c2sListeners = new ConcurrentHashMap[String, ReactivePubSubCommands.ReactiveRedisSubscriber]()
|
||||||
private val s2cObservers = new ConcurrentHashMap[String, Observer]()
|
private val s2cObservers = new ConcurrentHashMap[String, Observer]()
|
||||||
|
|
||||||
private def c2sTopic(gameId: String): String =
|
private def c2sTopic(gameId: String): String =
|
||||||
@@ -50,35 +52,37 @@ class GameRedisSubscriberManager:
|
|||||||
s"${redisConfig.prefix}:game:$gameId:s2c"
|
s"${redisConfig.prefix}:game:$gameId:s2c"
|
||||||
|
|
||||||
def subscribeGame(gameId: String): Unit =
|
def subscribeGame(gameId: String): Unit =
|
||||||
try
|
val writebackFn: String => Unit = json => redis.pubsub(classOf[String]).publish("game-writeback", json)
|
||||||
val handler: Consumer[String] = msg => handleC2sMessage(gameId, msg)
|
val obs = new GameRedisPublisher(
|
||||||
val subscriber = redis.pubsub(classOf[String]).subscribe(c2sTopic(gameId), handler)
|
gameId,
|
||||||
c2sListeners.put(gameId, subscriber)
|
registry,
|
||||||
|
redis,
|
||||||
|
objectMapper,
|
||||||
|
s2cTopicName(gameId),
|
||||||
|
writebackFn,
|
||||||
|
ioClient,
|
||||||
|
unsubscribeGame,
|
||||||
|
)
|
||||||
|
s2cObservers.put(gameId, obs)
|
||||||
|
registry.get(gameId).foreach(_.engine.subscribe(obs))
|
||||||
|
heartbeatServiceOpt.foreach(_.addGameSubscription(gameId))
|
||||||
|
|
||||||
val writebackFn: String => Unit = json => redis.pubsub(classOf[String]).publish("game-writeback", json)
|
val handler: Consumer[String] = msg => handleC2sMessage(gameId, msg)
|
||||||
val obs = new GameRedisPublisher(
|
reactiveRedis
|
||||||
gameId,
|
.pubsub(classOf[String])
|
||||||
registry,
|
.subscribe(c2sTopic(gameId), handler)
|
||||||
redis,
|
.subscribe()
|
||||||
objectMapper,
|
.`with`(
|
||||||
s2cTopicName(gameId),
|
subscriber => {
|
||||||
writebackFn,
|
c2sListeners.put(gameId, subscriber)
|
||||||
ioClient,
|
log.debugf("Subscribed to game %s", gameId)
|
||||||
unsubscribeGame,
|
},
|
||||||
|
failure => log.warnf(failure, "Redis subscription failed for game %s", gameId),
|
||||||
)
|
)
|
||||||
s2cObservers.put(gameId, obs)
|
|
||||||
registry.get(gameId).foreach(_.engine.subscribe(obs))
|
|
||||||
log.debugf("Subscribed to game %s", gameId)
|
|
||||||
|
|
||||||
heartbeatServiceOpt.foreach(_.addGameSubscription(gameId))
|
|
||||||
catch
|
|
||||||
case e: Exception =>
|
|
||||||
log.warnf(e, "Redis subscription failed for game %s", gameId)
|
|
||||||
()
|
|
||||||
|
|
||||||
def unsubscribeGame(gameId: String): Unit =
|
def unsubscribeGame(gameId: String): Unit =
|
||||||
Option(c2sListeners.remove(gameId)).foreach { subscriber =>
|
Option(c2sListeners.remove(gameId)).foreach { subscriber =>
|
||||||
subscriber.unsubscribe(c2sTopic(gameId))
|
subscriber.unsubscribe(c2sTopic(gameId)).subscribe().`with`(_ => (), _ => ())
|
||||||
}
|
}
|
||||||
Option(s2cObservers.remove(gameId)).foreach { obs =>
|
Option(s2cObservers.remove(gameId)).foreach { obs =>
|
||||||
registry.get(gameId).foreach(_.engine.unsubscribe(obs))
|
registry.get(gameId).foreach(_.engine.unsubscribe(obs))
|
||||||
@@ -154,5 +158,5 @@ class GameRedisSubscriberManager:
|
|||||||
|
|
||||||
@PreDestroy
|
@PreDestroy
|
||||||
def cleanup(): Unit =
|
def cleanup(): Unit =
|
||||||
c2sListeners.forEach((gameId, subscriber) => subscriber.unsubscribe(c2sTopic(gameId)))
|
c2sListeners.forEach((gameId, subscriber) => subscriber.unsubscribe(c2sTopic(gameId)).await().indefinitely())
|
||||||
s2cObservers.forEach((gameId, obs) => registry.get(gameId).foreach(_.engine.unsubscribe(obs)))
|
s2cObservers.forEach((gameId, obs) => registry.get(gameId).foreach(_.engine.unsubscribe(obs)))
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
MAJOR=0
|
MAJOR=0
|
||||||
MINOR=28
|
MINOR=29
|
||||||
PATCH=0
|
PATCH=0
|
||||||
|
|||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
package de.nowchess.security
|
||||||
|
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped
|
||||||
|
import jakarta.ws.rs.core.MultivaluedMap
|
||||||
|
import org.eclipse.microprofile.config.inject.ConfigProperty
|
||||||
|
import org.eclipse.microprofile.rest.client.ext.DefaultClientHeadersFactoryImpl
|
||||||
|
|
||||||
|
import scala.compiletime.uninitialized
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
class InternalClientHeadersFactory extends DefaultClientHeadersFactoryImpl {
|
||||||
|
|
||||||
|
@ConfigProperty(name = "nowchess.internal.secret", defaultValue = "")
|
||||||
|
// scalafix:off DisableSyntax.var
|
||||||
|
var secret: String = uninitialized
|
||||||
|
|
||||||
|
@ConfigProperty(name = "nowchess.internal.auth.enabled", defaultValue = "true")
|
||||||
|
var authEnabled: Boolean = uninitialized
|
||||||
|
// scalafix:on DisableSyntax.var
|
||||||
|
|
||||||
|
override def update(
|
||||||
|
incomingHeaders: MultivaluedMap[String, String],
|
||||||
|
clientOutgoingHeaders: MultivaluedMap[String, String],
|
||||||
|
): MultivaluedMap[String, String] = {
|
||||||
|
val default = super.update(incomingHeaders, clientOutgoingHeaders)
|
||||||
|
default.putSingle("X-Internal-Secret", secret)
|
||||||
|
default
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -61,3 +61,18 @@
|
|||||||
* **docker:** add Dockerfiles for building Quarkus application in native and JVM modes ([3f2d2bb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/3f2d2bb4c97fa8cddba66e1da4427c54236dfeed))
|
* **docker:** add Dockerfiles for building Quarkus application in native and JVM modes ([3f2d2bb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/3f2d2bb4c97fa8cddba66e1da4427c54236dfeed))
|
||||||
* **docker:** add Dockerfiles for Quarkus application in JVM and native modes ([34b9933](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/34b993304670cf2aa62cd2f6460cee7b9864b08e))
|
* **docker:** add Dockerfiles for Quarkus application in JVM and native modes ([34b9933](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/34b993304670cf2aa62cd2f6460cee7b9864b08e))
|
||||||
* true-microservices ([#40](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/40)) ([5909242](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/590924254e8a2754de661a57a03e43f89ceb6299))
|
* true-microservices ([#40](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/40)) ([5909242](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/590924254e8a2754de661a57a03e43f89ceb6299))
|
||||||
|
## (2026-05-05)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **config:** update application.yml for PostgreSQL and remove staging/production configurations ([2404e61](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/2404e6164c3b50ffccbea5238d636060d6abe4d6))
|
||||||
|
* **config:** update application.yml for staging and production environments ([6113432](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/6113432a14c476a3a0dfc0d449e17d023697f2ba))
|
||||||
|
* **config:** update application.yml to nest HTTP port configuration ([3efebd5](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/3efebd5ed0493159c51f7246d18d59bac58cf875))
|
||||||
|
* **docker:** add .dockerignore and .gitignore files for build exclusions ([c987d8e](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/c987d8e258c0e6c4cfbdaa8381c64c410d7a2b83))
|
||||||
|
* **docker:** add Dockerfiles for building Quarkus application in native and JVM modes ([3f2d2bb](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/3f2d2bb4c97fa8cddba66e1da4427c54236dfeed))
|
||||||
|
* **docker:** add Dockerfiles for Quarkus application in JVM and native modes ([34b9933](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/34b993304670cf2aa62cd2f6460cee7b9864b08e))
|
||||||
|
* true-microservices ([#40](https://git.janis-eccarius.de/NowChess/NowChessSystems/issues/40)) ([5909242](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/590924254e8a2754de661a57a03e43f89ceb6299))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **redis:** update Redis configuration with max pool size and waiting parameters ([5baf6a7](https://git.janis-eccarius.de/NowChess/NowChessSystems/commit/5baf6a7cdbea484fc49c02e2b5a1c3919b7fa2c4))
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ quarkus:
|
|||||||
enabled: true
|
enabled: true
|
||||||
redis:
|
redis:
|
||||||
hosts: redis://${REDIS_HOST:localhost}:${REDIS_PORT:6379}
|
hosts: redis://${REDIS_HOST:localhost}:${REDIS_PORT:6379}
|
||||||
|
max-pool-size: 64
|
||||||
|
max-pool-waiting: 128
|
||||||
datasource:
|
datasource:
|
||||||
db-kind: postgresql
|
db-kind: postgresql
|
||||||
username: ${DB_USER:nowchess}
|
username: ${DB_USER:nowchess}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
MAJOR=0
|
MAJOR=0
|
||||||
MINOR=6
|
MINOR=7
|
||||||
PATCH=0
|
PATCH=0
|
||||||
|
|||||||
@@ -26,4 +26,5 @@ include(
|
|||||||
"modules:ws",
|
"modules:ws",
|
||||||
"modules:store",
|
"modules:store",
|
||||||
"modules:coordinator",
|
"modules:coordinator",
|
||||||
|
"modules:benchmarks",
|
||||||
)
|
)
|
||||||
Reference in New Issue
Block a user