Merge pull request #168 from optuna/server-option

Add --server option for Gunicorn support
This commit is contained in:
Masashi Shibata
2022-02-17 22:15:48 +09:00
committed by GitHub
4 changed files with 86 additions and 24 deletions
+1 -1
View File
@@ -40,7 +40,7 @@ You can use the Docker image like below:
```
# SQLite3
$ docker run -it --rm -p 8080:8080 -v `PWD`:/app -w /app optuna-dashboard sqlite:///db.sqlite3
$ docker run -it --rm -p 8080:8080 -v `PWD`:/app -w /app sqlite:///db.sqlite3
```
### Running dashboard server
+2 -2
View File
@@ -15,7 +15,7 @@ FROM python:3.8-buster AS python-builder
WORKDIR /usr/src
RUN pip install --upgrade pip setuptools
RUN pip install --progress-bar off PyMySQL[rsa] psycopg2-binary
RUN pip install --progress-bar off PyMySQL[rsa] psycopg2-binary gunicorn
ADD ./setup.cfg /usr/src/setup.cfg
ADD ./setup.py /usr/src/setup.py
@@ -32,5 +32,5 @@ RUN mkdir /app
WORKDIR /app
EXPOSE 8080
ENTRYPOINT ["optuna-dashboard", "--port", "8080", "--host", "0.0.0.0"]
ENTRYPOINT ["optuna-dashboard", "--port", "8080", "--host", "0.0.0.0", "--server", "gunicorn"]
+39 -19
View File
@@ -29,6 +29,23 @@ Listening on http://localhost:8080/
Hit Ctrl-C to quit.
```
<details>
<summary>Running optuna-dashboard with Gunicorn</summary>
optuna-dashboard uses [wsgiref](https://docs.python.org/3/library/wsgiref.html) module
which is provided as a Python standard library. But it has not been reviewed for security
issues, so not suitable for the production use. You can run optuna-dashboard with Gunicorn
more secure and/or more fast.
```console
$ pip install gunicorn
$ optuna-dashboard sqlite:///db.sqlite3 --server gunicorn
```
</details>
<details>
<summary>More command line options</summary>
@@ -43,15 +60,32 @@ positional arguments:
storage DB URL (e.g. sqlite:///example.db)
optional arguments:
-h, --help show this help message and exit
--port PORT port number (default: 8080)
--host HOST hostname (default: 127.0.0.1)
--version, -v show program's version number and exit
--quiet, -q quiet
-h, --help show this help message and exit
--port PORT port number (default: 8080)
--host HOST hostname (default: 127.0.0.1)
--server {wsgiref,gunicorn}
server (default: wsgiref)
--version, -v show program's version number and exit
--quiet, -q quiet
```
</details>
<details>
<summary>Python Interface</summary>
**`run_server(storage: Union[str, BaseStorage], host: str = 'localhost', port: int = 8080) -> NoReturn`**
Start running optuna-dashboard and blocks until the server terminates.
This function uses wsgiref module which is not intended for the production use.
**`wsgi(storage: Union[str, BaseStorage]) -> WSGIApplication`**
This function exposes WSGI interface for people who want to run on the
production-class WSGI servers like Gunicorn or uWSGI.
</details>
## Using an official Docker image
@@ -102,20 +136,6 @@ You can walk-through trials by filtering and sorting.
![optuna-dashboard-trials-datagrid](https://user-images.githubusercontent.com/5564044/114265667-20d57d00-9a2d-11eb-8b9c-69541c9b4a28.gif)
## Python Interface
### `run_server(storage: Union[str, BaseStorage], host: str = 'localhost', port: int = 8080) -> NoReturn`
Start running optuna-dashboard and blocks until the server terminates.
This function uses wsgiref module which is not intended for the production
use. If you want to run optuna-dashboard more secure and/or more fast,
please use WSGI server like Gunicorn or uWSGI via `wsgi()` function.
### `wsgi(storage: Union[str, BaseStorage]) -> WSGIApplication`
This function exposes WSGI interface for people who want to run on the
production-class WSGI servers like Gunicorn or uWSGI.
## Submitting patches
If you want to contribute, please check [Developers Guide](./DEVELOPMENT.md).
+44 -2
View File
@@ -1,6 +1,8 @@
import argparse
import os
from typing import NoReturn
from bottle import Bottle
from bottle import run
from optuna.storages import BaseStorage
from optuna.storages import RDBStorage
@@ -11,9 +13,38 @@ from .app import create_app
AUTO_RELOAD = os.environ.get("OPTUNA_DASHBOARD_AUTO_RELOAD") == "1"
SERVER_CHOICES = ["wsgiref", "gunicorn"]
def main() -> None:
def run_wsgiref(app: Bottle, host: str, port: int, quiet: bool) -> NoReturn: # type: ignore
run(
app,
host=host,
port=port,
server="wsgiref",
quiet=quiet,
reloader=AUTO_RELOAD,
)
def run_gunicorn(app: Bottle, host: str, port: int, quiet: bool) -> NoReturn: # type: ignore
# See https://docs.gunicorn.org/en/latest/custom.html
from gunicorn.app.base import BaseApplication
class _Application(BaseApplication):
def load_config(self) -> None:
self.cfg.set("bind", f"{host}:{port}")
if quiet:
self.cfg.set("loglevel", "error")
def load(self) -> Bottle:
return app
_Application().run()
def main() -> NoReturn:
parser = argparse.ArgumentParser(description="Real-time dashboard for Optuna.")
parser.add_argument("storage", help="DB URL (e.g. sqlite:///example.db)", type=str)
parser.add_argument(
@@ -22,6 +53,12 @@ def main() -> None:
parser.add_argument(
"--host", help="hostname (default: %(default)s)", default="127.0.0.1"
)
parser.add_argument(
"--server",
help="server (default: %(default)s)",
default="wsgiref",
choices=SERVER_CHOICES,
)
parser.add_argument("--version", "-v", action="version", version=__version__)
parser.add_argument("--quiet", "-q", help="quiet", action="store_true")
args = parser.parse_args()
@@ -33,7 +70,12 @@ def main() -> None:
storage = RDBStorage(args.storage)
app = create_app(storage)
run(app, host=args.host, port=args.port, quiet=args.quiet, reloader=AUTO_RELOAD)
if args.server == "wsgiref":
run_wsgiref(app, args.host, args.port, args.quiet)
elif args.server == "gunicorn":
run_gunicorn(app, args.host, args.port, args.quiet)
else:
raise Exception("must not reach here")
if __name__ == "__main__":