Sign inSign up

imperialqore/ldes-rest

By imperialqore

Updated about 1 month ago

REST API for LDES (LINE Discrete Event Simulator). Source: github.com/imperial-qore/ldes

Image
0

387

imperialqore/ldes-rest repository overview

ldes-rest (Docker image)

Docker packaging of LDES, the discrete-event simulator of the LINE solver, with a REST interface. The image bundles a JRE, so nothing has to be installed on the host: it reads a queueing network model in JSON, simulates it, and returns performance metrics in JSON.

The engine is the GraalVM native image of LDES, executed as a subprocess per request. It avoids JVM startup entirely, so an M/M/1 model with 20000 events answers in roughly 60 ms end to end, against roughly 280 ms of JVM startup alone for java -jar ldes.jar. Each solve being its own process is also what allows the server to run several concurrently.

Usage

REST server (default)
docker run --rm -p 8080:8080 imperialqore/ldes-rest
curl -s -X POST http://localhost:8080/api/v1/solve \
     -H 'Content-Type: application/json' \
     -d '{"model":{"content":"<model.json text>"},"options":{"samples":20000,"seed":42}}'
Command line
docker run --rm -v "$PWD:/data" imperialqore/ldes-rest solve model.json -o result.json --seed 42
docker run --rm -v "$PWD:/data" imperialqore/ldes-rest model.json -o result.json   # solve is implicit

The container runs as an unprivileged user, so a bind-mounted output directory must be writable by others, or the container must be started with --user "$(id -u):$(id -g)".

API

Base path /api/v1.

MethodPathDescription
GET/healthLiveness probe
GET/readyReadiness probe (engine class resolvable)
GET/infoServer, engine and JVM metadata
POST/solveSimulate a model, return the ldes-result document
POST /api/v1/solve
{
  "model":   {"content": "<model.json text>", "base64": false},
  "options": {"samples": 20000, "seed": 42, "cnvgon": true, "cnvgtol": 0.01},
  "flags":   ["--trajectory"]
}

model also accepts an inline model object, or a bare JSON string. Each key of options maps to the LDES long-form flag of the same name: a number or string becomes --key value, a boolean true becomes the bare switch --key, an array is comma-joined (--timespan, --initsol). flags is a verbatim escape hatch for anything the mapping does not cover. -o and --help are rejected: the server owns the model and result paths.

The response embeds the same ldes-result document the CLI writes to -o:

{
  "status": "ok",
  "exitCode": 0,
  "elapsedMs": 21,
  "result": {"format": "ldes-result", "metrics": {"QN": [[0.0], [0.973]], "...": []}},
  "stdout": "LDES events: ...",
  "stderr": ""
}

Status codes: 200 on success, 400 for a malformed request, 422 when the engine rejects the model or produces no result, 503 from /ready before the engine is resolvable.

Configuration

VariableDefaultMeaning
LDES_REST_HOST0.0.0.0Bind address
LDES_REST_PORT8080Bind port
LDES_REST_CONCURRENCYCPU countConcurrent engine subprocesses
LDES_REST_TEMP_DIR/tmp/ldes-restScratch directory for per-request documents
LDES_REST_MAX_BODY67108864Maximum request body in bytes
LDES_ENGINE/opt/ldes/ldesPath to the LDES native image
JAVA_OPTSemptyExtra JVM flags for the front end

Concurrency bounds the engine subprocesses, not the HTTP threads: requests beyond the limit queue rather than fail. Large state spaces are bounded by the engine's own memory, not by the front end's heap, so JAVA_OPTS does not need raising for big models.

Calling the server from LINE

All three LINE bindings take an explicit solver option naming the server. The model is serialized to the same model.json the CLI reads, so for a fixed seed and event budget the remote run is bit-for-bit identical to a local one.

% MATLAB
opts = SolverOptions('LDES');
opts.rest_url = 'http://localhost:8080';
avgTable = SolverLDES(model, opts).getAvgTable();
# Python
from line_solver.solvers.wrappers.solver_ldes import SolverLDES
SolverLDES(model, samples=20000, seed=42, rest_url='http://localhost:8080').getAvgTable()
// Java
LDESOptions opts = new LDESOptions();
opts.setRestUrl("http://localhost:8080");
new SolverLDES(model, opts).getAvgTable();

Build and test

./build.sh                       # stage the artifacts, then docker build
./build.sh /path/to/ldes/target imperialqore/ldes-rest:3.0.6
tests/smoke.sh                   # CLI mode, REST mode, error handling

build.sh takes both artifacts from ../line-dev.git/ldes/target, falling back to ../ldes-standalone.git: the native image ldes is the engine, and ldes.jar is a build-time input supplying the Gson classes the front end needs, so nothing is fetched from a network during the build. It refuses an engine that is not an x86-64 ELF executable, and a jar whose manifest does not name jline.cli.LdesCLI, since concurrent bundle builds can leave either file half-written.

line-dev.git/upload-ldes.sh runs this build after the GraalVM build and pushes the image alongside the SourceForge and stand-alone artifacts, so the published image always carries the engine of the current LINE release. Pass --no-docker to skip that step.

tests/JavaRestParity.java and tests/matlab_rest_parity.m check that a remote solve matches a local one exactly; both take the server URL as their argument.

Original project: https://line-solver.sourceforge.net/

Source: https://github.com/imperial-qore/ldes

License: BSD-3-Clause

Tag summary

Content type

Image

Digest

sha256:9b5adb9f3

Size

90.7 MB

Last updated

about 1 month ago

docker pull imperialqore/ldes-rest