Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,52 @@ This file sets up the server and runs it from the given port in main.
]
```

- `/health` :
- Returns the current health and status of the UWAPI middleware and its dependant backend services.

UWAPI health fields:
- `status`: (String) Always `"ok"`. Indicates that UWAPI is online.
- `http_code`: (Integer) Always HTTP `200`. Indicates that UWAPI is online.
- `timestamp`: (Integer) ISO 8601 UTC timestamp of when the health response was made (e.g., `"2025-05-16T20:25:55Z"`).
- `uptime`: (String) Time UWAPI has been running in `Xd Yh Zm Ws` format.
- `cpu_usage`: (String) The percentage of CPU currently used (e.g., `"21.2%"`).
- `memory_usage`: (String) The percentage of total system memory currently used (e.g., `"10.1%"`).
- `process_count`: (String) Number of processes currently running on the system.
- `backend_services`: (Object) An object describing the status of each backend service UWAPI depends on.

For each available backend service the following health fields are appended `backend_services`:
- `status`: (String) `"ok"` if the service responded successfully; `"fail"` if it did not respond or returned an error.
- `http_code`: (Integer) Service healthcheck response HTTP code.
- `latency`: (Integer) Present only if the healthcheck succeeded. Round-trip latency in milliseconds to reach the service.

Below is an example response from `/health`:

```json
{
"backend_services": {
"gamesmanclassic": {
"http_code": 200,
"latency_ms": 109,
"status": "ok"
},
"gamesmanone": {
"status": "fail"
},
"gamesmanpuzzles": {
"http_code": 200,
"latency_ms": 40,
"status": "ok"
}
},
"cpu_usage": "6.1%",
"memory_usage": "58.5%",
"process_count": 887,
"status": "ok",
"timestamp": "2025-05-16T20:47:47Z",
"uptime": "0d 0h 0m 1s"
}
```

- `/<game_id>/` :

- Returns general information about the game specified by `game_id`. This is used, for example, by GamesmanUni when a game is clicked in order to see which variants are available in order to render the list of available variants.
Expand Down
68 changes: 68 additions & 0 deletions server.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,21 @@
from games.models import Value, Remoteness
from md_api import md_instr

from games.gamesman_classic import GamesmanClassic
from games.gamesman_one import GamesmanOne
from games.gamesman_puzzles import GamesmanPuzzles
import requests

import time
import psutil
from datetime import datetime, timezone

app = Flask(__name__)
CORS(app)

start_time = time.time()
BACKEND_SERVICES = [GamesmanClassic, GamesmanPuzzles, GamesmanOne]

# Helper Functions

def error(a):
Expand Down Expand Up @@ -165,6 +177,13 @@ def wrangle_move_objects_2Player(position_data):
move_objs.sort(key=key_move_obj_by_move_value_then_delta_remoteness)


def format_time(seconds: float) -> str:
seconds = int(seconds)
days = seconds // 86400
hours = (seconds % 86400) // 3600
minutes = (seconds % 3600) // 60
secs = seconds % 60
return f"{days}d {hours}h {minutes}m {secs}s"
# Routes

@app.route("/")
Expand All @@ -178,6 +197,55 @@ def get_games() -> list[dict[str, str]]:
all_games.sort(key=lambda g: g['name'])
return jsonify(all_games)

@app.route("/health")
def get_health():
uptime_seconds = time.time() - start_time
uptime = format_time(uptime_seconds)
cpu_usage = psutil.cpu_percent(interval=0.1)
memory = psutil.virtual_memory()
memory_usage = f"{memory.percent}%"
process_count = len(psutil.pids())
timestamp = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace('+00:00', 'Z')

services_status = {}

for service_cls in BACKEND_SERVICES:
service_name = service_cls.__name__.lower()
try:
start = time.time()
health_url = f"{service_cls.url}health"
res = requests.get(health_url, timeout=1.5)
latency_ms = round((time.time() - start) * 1000)
if res.status_code == 200:
services_status[service_name] = {
"status": "ok",
"http_code": res.status_code,
"latency_ms": latency_ms
}
else:
services_status[service_name] = {
"status": "fail",
"http_code": res.status_code,
"latency_ms": latency_ms,
"error": f"Status code {res.status_code}"
}
except Exception as e:
services_status[service_name] = {
"status": "fail",
}

payload = {
"status": "ok",
"uptime": uptime,
"cpu_usage": f"{cpu_usage}%",
"memory_usage": memory_usage,
"process_count": process_count,
"timestamp": timestamp,
"backend_services": services_status
}

return jsonify(payload), 200

@app.route("/<game_id>/")
def get_game(game_id: str):
if game_id in games:
Expand Down