mirror of
https://github.com/LibreTranslate/LibreTranslate.git
synced 2024-11-21 15:31:00 +00:00
Prometheus support
This commit is contained in:
parent
753a33e7c5
commit
9f243fd6be
7 changed files with 120 additions and 7 deletions
45
README.md
45
README.md
|
@ -196,7 +196,9 @@ docker-compose -f docker-compose.cuda.yml up -d --build
|
|||
| --suggestions | Allow user suggestions | `false` | LT_SUGGESTIONS |
|
||||
| --disable-files-translation | Disable files translation | `false` | LT_DISABLE_FILES_TRANSLATION |
|
||||
| --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 | `false` | 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.
|
||||
|
||||
|
@ -267,6 +269,47 @@ ltmanage keys remove <api-key>
|
|||
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
|
||||
|
||||
You can use the LibreTranslate API using the following bindings:
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
1.3.4
|
||||
1.3.5
|
||||
|
|
53
app/app.py
53
app/app.py
|
@ -4,15 +4,17 @@ import tempfile
|
|||
import uuid
|
||||
from functools import wraps
|
||||
from html import unescape
|
||||
from timeit import default_timer
|
||||
|
||||
import argostranslatefiles
|
||||
from argostranslatefiles import get_supported_formats
|
||||
from flask import (Flask, abort, jsonify, render_template, request, send_file,
|
||||
url_for)
|
||||
url_for, Response)
|
||||
from flask_swagger import swagger
|
||||
from flask_swagger_ui import get_swaggerui_blueprint
|
||||
from translatehtml import translate_html
|
||||
from werkzeug.utils import secure_filename
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
from app import flood, remove_translated_files, security
|
||||
from app.language import detect_languages, improve_translation_formatting
|
||||
|
@ -174,6 +176,28 @@ def create_app(args):
|
|||
if args.req_flood_threshold > 0:
|
||||
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):
|
||||
@wraps(f)
|
||||
def func(*a, **kw):
|
||||
|
@ -203,11 +227,30 @@ def create_app(args):
|
|||
403,
|
||||
description=description,
|
||||
)
|
||||
|
||||
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)
|
||||
def invalid_api(e):
|
||||
return jsonify({"error": str(e.description)}), 400
|
||||
|
|
|
@ -160,6 +160,16 @@ _default_options_objects = [
|
|||
'name': 'UPDATE_MODELS',
|
||||
'default_value': False,
|
||||
'value_type': 'bool'
|
||||
},
|
||||
{
|
||||
'name': 'METRICS',
|
||||
'default_value': False,
|
||||
'value_type': 'bool'
|
||||
},
|
||||
{
|
||||
'name': 'METRICS_AUTH_TOKEN',
|
||||
'default_value': '',
|
||||
'value_type': 'str'
|
||||
},
|
||||
]
|
||||
|
||||
|
|
12
app/main.py
12
app/main.py
|
@ -147,6 +147,18 @@ def get_args():
|
|||
parser.add_argument(
|
||||
"--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()
|
||||
|
||||
|
||||
|
|
4
gunicorn_conf.py
Normal file
4
gunicorn_conf.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
from prometheus_client import multiprocess
|
||||
|
||||
def child_exit(server, worker):
|
||||
multiprocess.mark_process_dead(worker.pid)
|
|
@ -15,3 +15,4 @@ itsdangerous==2.1.2
|
|||
Werkzeug==2.2.2
|
||||
requests==2.28.1
|
||||
redis==4.3.4
|
||||
prometheus-client==0.15.0
|
Loading…
Reference in a new issue