Merge pull request #368 from pierotofy/prom

Prometheus Support
This commit is contained in:
Piero Toffanin 2022-12-26 16:21:46 -05:00 committed by GitHub
commit b6111b95f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 123 additions and 10 deletions

View file

@ -193,10 +193,12 @@ docker-compose -f docker-compose.cuda.yml up -d --build
| --require-api-key-origin | Require use of an API key for programmatic access to the API, unless the request origin matches this domain | `No restrictions on domain origin` | LT_REQUIRE_API_KEY_ORIGIN | | --require-api-key-origin | Require use of an API key for programmatic access to the API, unless the request origin matches this domain | `No restrictions on domain origin` | LT_REQUIRE_API_KEY_ORIGIN |
| --load-only | Set available languages | `all from argostranslate` | LT_LOAD_ONLY | | --load-only | Set available languages | `all from argostranslate` | LT_LOAD_ONLY |
| --threads | Set number of threads | `4` | LT_THREADS | | --threads | Set number of threads | `4` | LT_THREADS |
| --suggestions | Allow user suggestions | `false` | LT_SUGGESTIONS | | --suggestions | Allow user suggestions | `False` | LT_SUGGESTIONS |
| --disable-files-translation | Disable files translation | `false` | LT_DISABLE_FILES_TRANSLATION | | --disable-files-translation | Disable files translation | `False` | LT_DISABLE_FILES_TRANSLATION |
| --disable-web-ui | Disable web ui | `false` | LT_DISABLE_WEB_UI | | --disable-web-ui | Disable web ui | `False` | LT_DISABLE_WEB_UI |
| --update-models | Update language models at startup | `false` | LT_UPDATE_MODELS | | --update-models | Update language models at startup | `False` | LT_UPDATE_MODELS |
| --metrics | Enable the /metrics endpoint for exporting [Prometheus](https://prometheus.io/) usage metrics | `Disabled` | LT_METRICS |
| --metrics-auth-token | Protect the /metrics endpoint by allowing only clients that have a valid Authorization Bearer token | `No auth` | LT_METRICS_AUTH_TOKEN |
Note that each argument has an equivalent environment variable that can be used instead. The env. variables overwrite the default values but have lower priority than the command arguments and are particularly useful if used with Docker. The environment variable names are the upper-snake-case of the equivalent command argument's name with a `LT` prefix. Note that each argument has an equivalent environment variable that can be used instead. The env. variables overwrite the default values but have lower priority than the command arguments and are particularly useful if used with Docker. The environment variable names are the upper-snake-case of the equivalent command argument's name with a `LT` prefix.
@ -267,6 +269,47 @@ ltmanage keys remove <api-key>
ltmanage keys ltmanage keys
``` ```
## Prometheus Metrics
LibreTranslate has Prometheus [exporter](https://prometheus.io/docs/instrumenting/exporters/) capabilities when you pass the `--metrics` argument at startup (disabled by default). When metrics are enabled, a `/metrics` endpoint is mounted on the instance:
http://localhost:5000/metrics
```
# HELP request_inprogress Multiprocess metric
# TYPE request_inprogress gauge
request_inprogress{api_key="",endpoint="/translate",request_ip="127.0.0.1"} 0.0
# HELP request_seconds Multiprocess metric
# TYPE request_seconds summary
request_seconds_count{api_key="",endpoint="/translate",request_ip="127.0.0.1",status="200"} 0.0
request_seconds_sum{api_key="",endpoint="/translate",request_ip="127.0.0.1",status="200"} 0.0
```
You can then configure `prometheus.yml` to read the metrics:
```
scrape_configs:
- job_name: "libretranslate"
# Needed only if you use --metrics-auth-token
#authorization:
#credentials: "mytoken"
static_configs:
- targets: ["localhost:5000"]
```
To secure the `/metrics` endpoint you can also use `--metrics-auth-token mytoken`.
If you use Gunicorn, make sure to create a directory for storing multiprocess data metrics and set `PROMETHEUS_MULTIPROC_DIR`:
```
mkdir -p /tmp/prometheus_data
rm /tmp/prometheus_data/*
export PROMETHEUS_MULTIPROC_DIR=/tmp/prometheus_data
gunicorn -c gunicorn_conf.py --bind 0.0.0.0:5000 'wsgi:app(metrics=True)'
```
## Language Bindings ## Language Bindings
You can use the LibreTranslate API using the following bindings: You can use the LibreTranslate API using the following bindings:

View file

@ -1 +1 @@
1.3.4 1.3.5

View file

@ -4,15 +4,17 @@ import tempfile
import uuid import uuid
from functools import wraps from functools import wraps
from html import unescape from html import unescape
from timeit import default_timer
import argostranslatefiles import argostranslatefiles
from argostranslatefiles import get_supported_formats from argostranslatefiles import get_supported_formats
from flask import (Flask, abort, jsonify, render_template, request, send_file, from flask import (Flask, abort, jsonify, render_template, request, send_file,
url_for) url_for, Response)
from flask_swagger import swagger from flask_swagger import swagger
from flask_swagger_ui import get_swaggerui_blueprint from flask_swagger_ui import get_swaggerui_blueprint
from translatehtml import translate_html from translatehtml import translate_html
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from werkzeug.exceptions import HTTPException
from app import flood, remove_translated_files, security from app import flood, remove_translated_files, security
from app.language import detect_languages, improve_translation_formatting from app.language import detect_languages, improve_translation_formatting
@ -174,6 +176,28 @@ def create_app(args):
if args.req_flood_threshold > 0: if args.req_flood_threshold > 0:
flood.setup(args.req_flood_threshold) flood.setup(args.req_flood_threshold)
measure_request = None
gauge_request = None
if args.metrics:
from prometheus_client import CONTENT_TYPE_LATEST, Summary, Gauge, CollectorRegistry, multiprocess, generate_latest
@app.route("/metrics")
def prometheus_metrics():
if args.metrics_auth_token:
authorization = request.headers.get('Authorization')
if authorization != "Bearer " + args.metrics_auth_token:
abort(401, description="Unauthorized")
registry = CollectorRegistry()
multiprocess.MultiProcessCollector(registry)
return Response(generate_latest(registry), mimetype=CONTENT_TYPE_LATEST)
measure_request = Summary('request_seconds', 'Time spent on request', ['endpoint', 'status', 'request_ip', 'api_key'])
measure_request.labels('/translate', 200, '127.0.0.1', '')
gauge_request = Gauge('request_inprogress', 'Active requests', ['endpoint', 'request_ip', 'api_key'], multiprocess_mode='livesum')
gauge_request.labels('/translate', '127.0.0.1', '')
def access_check(f): def access_check(f):
@wraps(f) @wraps(f)
def func(*a, **kw): def func(*a, **kw):
@ -203,10 +227,29 @@ def create_app(args):
403, 403,
description=description, description=description,
) )
return f(*a, **kw) return f(*a, **kw)
return func if args.metrics:
@wraps(func)
def measure_func(*a, **kw):
start_t = default_timer()
status = 200
ip = get_remote_address()
ak = get_req_api_key() or ''
g = gauge_request.labels(request.path, ip, ak)
try:
g.inc()
return func(*a, **kw)
except HTTPException as e:
status = e.code
raise e
finally:
duration = max(default_timer() - start_t, 0)
measure_request.labels(request.path, status, ip, ak).observe(duration)
g.dec()
return measure_func
else:
return func
@app.errorhandler(400) @app.errorhandler(400)
def invalid_api(e): def invalid_api(e):

View file

@ -161,6 +161,16 @@ _default_options_objects = [
'default_value': False, 'default_value': False,
'value_type': 'bool' 'value_type': 'bool'
}, },
{
'name': 'METRICS',
'default_value': False,
'value_type': 'bool'
},
{
'name': 'METRICS_AUTH_TOKEN',
'default_value': '',
'value_type': 'str'
},
] ]

View file

@ -147,6 +147,18 @@ def get_args():
parser.add_argument( parser.add_argument(
"--update-models", default=DEFARGS['UPDATE_MODELS'], action="store_true", help="Update language models at startup" "--update-models", default=DEFARGS['UPDATE_MODELS'], action="store_true", help="Update language models at startup"
) )
parser.add_argument(
"--metrics",
default=DEFARGS['METRICS'],
action="store_true",
help="Enable the /metrics endpoint for exporting Prometheus usage metrics",
)
parser.add_argument(
"--metrics-auth-token",
default=DEFARGS['METRICS_AUTH_TOKEN'],
type=str,
help="Protect the /metrics endpoint by allowing only clients that have a valid Authorization Bearer token (%(default)s)",
)
return parser.parse_args() return parser.parse_args()

4
gunicorn_conf.py Normal file
View file

@ -0,0 +1,4 @@
from prometheus_client import multiprocess
def child_exit(server, worker):
multiprocess.mark_process_dead(worker.pid)

View file

@ -15,3 +15,4 @@ itsdangerous==2.1.2
Werkzeug==2.2.2 Werkzeug==2.2.2
requests==2.28.1 requests==2.28.1
redis==4.3.4 redis==4.3.4
prometheus-client==0.15.0