OpenTelemetry reference
This page lists the telemetry produced by DiracX: configuration, resource attributes, spans and metrics. See the OpenTelemetry explanation for the reasoning behind it, and the monitoring how-to guides to use it.
Configuration
DiracX settings
| Environment variable | Default | Description |
|---|---|---|
DIRACX_OTEL_ENABLED |
false |
Send telemetry to an OpenTelemetry collector |
DIRACX_OTEL_APPLICATION_NAME |
diracx |
Value of the service.name resource attribute |
DIRACX_OTEL_PROTOCOL |
grpc |
Protocol used to send the data to the collector: grpc, or http (protobuf encoded) |
DIRACX_OTEL_GRPC_ENDPOINT |
With grpc: address of the collector OTLP/gRPC receiver, e.g. otel-collector:4317 |
|
DIRACX_OTEL_GRPC_INSECURE |
true |
With grpc: do not use TLS to talk to the collector |
DIRACX_OTEL_HTTP_ENDPOINT |
With http: base URL of the collector OTLP/HTTP receiver, e.g. http://otel-collector:4318. /v1/traces, /v1/metrics and /v1/logs are appended to it, and the scheme decides whether TLS is used |
|
DIRACX_OTEL_HEADERS |
JSON dictionary of headers sent to the collector, e.g. {"tenant_id": "lhcbdiracx-cert"} |
These settings are read by the long running processes: API servers, scheduler and workers. The diracx-tasks call and diracx-tasks submit commands do not send telemetry.
Sending telemetry requires the otel extra of diracx-core (diracx-core[otel]), which the DiracX container images include.
If the endpoint of the chosen protocol is empty, the standard OTEL_EXPORTER_OTLP_ENDPOINT variables (or their defaults, localhost:4317 and http://localhost:4318) are used.
They are also listed with the other environment variables.
Standard OpenTelemetry variables
The standard OpenTelemetry SDK variables are honoured, in particular:
| Environment variable | Default | Description |
|---|---|---|
OTEL_RESOURCE_ATTRIBUTES |
Additional resource attributes, e.g. deployment.environment.name=production |
|
OTEL_TRACES_SAMPLER |
parentbased_always_on |
Sampler, e.g. parentbased_traceidratio |
OTEL_TRACES_SAMPLER_ARG |
Argument of the sampler, e.g. 0.1 to keep 10% of the traces |
|
OTEL_METRIC_EXPORT_INTERVAL |
60000 |
Milliseconds between two exports of the metrics |
OTEL_PYTHON_FASTAPI_EXCLUDED_URLS |
api/health/ |
Comma separated regular expressions of the URLs which are not instrumented (neither traced nor measured) |
Local development
| Environment variable | Default | Description |
|---|---|---|
DIRACX_LOCAL_OTEL_PROTOCOL |
grpc |
Protocol used by DiracX to send the data to the minimal collector started by pixi run local-start --otel: grpc or http |
DIRACX_LOCAL_OTEL_PORT |
4317 |
OTLP/gRPC port of the minimal collector |
DIRACX_LOCAL_OTEL_HTTP_PORT |
4318 |
OTLP/HTTP port of the minimal collector |
Resource attributes
Attached to all the spans, metrics and logs of a process.
| Attribute | Example | Description |
|---|---|---|
service.name |
diracx |
DIRACX_OTEL_APPLICATION_NAME |
service.version |
0.5.0 |
Version of diracx-core |
service.instance.id |
diracx-demo-7c9d7d8b4f-xk2lp-7 |
Host name (the pod name in kubernetes) and process ID: unique per process |
host.name |
diracx-demo-7c9d7d8b4f-xk2lp |
Host name (the pod name in kubernetes) |
process.pid |
7 |
Process ID |
diracx.component |
routers |
routers, tasks-worker or tasks-scheduler |
Spans
HTTP requests
Created by the OpenTelemetry FastAPI instrumentation, following the HTTP semantic conventions.
| Span | Kind | Attributes |
|---|---|---|
<method> <route>, e.g. GET /api/jobs/{job_id} |
SERVER | http.request.method, http.route, http.response.status_code, url.path, client.address, ... |
DiracX adds diracx.client.version, the version of the DiracX client (DiracX-Client-Version header, none if absent).
Once the access token is validated, it also adds:
| Attribute | Description |
|---|---|
enduser.id |
sub of the token |
diracx.vo |
VO of the token |
diracx.group |
DIRAC group of the token |
SQL queries
Following the database semantic conventions. Only created within another span (a request or a task).
| Span | Kind | Attributes |
|---|---|---|
<operation> <database>, e.g. SELECT JobDB |
CLIENT | db.system.name (mysql, sqlite), db.namespace (database), db.operation.name (SELECT, INSERT, ...), db.query.text (parametrised query, truncated to 2000 characters), server.address, server.port |
A failed query has the ERROR status, the error.type attribute (the exception class), and an exception event with the traceback.
Tasks
| Span | Kind | Emitted by | Description |
|---|---|---|---|
task.submit <task> |
PRODUCER | whoever submits | Submission of a task to the broker. Its context is stored in the message. |
task.process <task> |
CONSUMER | worker | Whole handling of a message: execution, retry or dead letter queue, callbacks, result storage. Root of a new trace, linked to task.submit (or, for a retry, to the attempt which failed). |
task.execute <task> |
INTERNAL | worker | Execution of the task code. Child of task.process. |
<task> is the name of the task in the registry, e.g. jobs:CleanSandboxStoreTask.
task.submit attributes:
| Attribute | Description |
|---|---|
task.name |
Name of the task |
task.id |
ID of the task |
task.priority |
realtime, normal or background |
task.size |
small, medium or large |
task.delayed |
true if the task is scheduled for later |
task.run_at |
When the task is scheduled for, if delayed |
task.process attributes:
| Attribute | Description |
|---|---|
task.name, task.id, task.priority, task.size |
As above |
task.retry_count |
Number of previous attempts |
task.queue_wait_s |
Seconds spent in the stream before being picked up (not set for reclaimed messages) |
task.reclaimed |
true if the message was taken over from a worker which did not acknowledge it in time |
messaging.destination.name |
Stream, e.g. diracx:tasks:normal:small |
messaging.message.id |
ID of the message in the stream |
and events:
| Event | Attributes | Description |
|---|---|---|
task.retry_scheduled |
task.retry.reason (error), task.retry.attempt, task.retry.at, task.retry.task_id |
The task failed and will be retried |
task.given_up |
task.action (dlq, discarded, dlq_failed) |
The task exhausted its retries |
task.execute attributes:
| Attribute | Description |
|---|---|
task.name, task.id, task.priority, task.size, task.retry_count |
As above |
task.status |
ok, error, lock_contention if a lock or limiter could not be acquired, or cancelled (e.g. the worker is shutting down) |
task.duration_ms |
Execution time |
error.type |
Exception class, if the task failed |
A failed task has the ERROR status and an exception event with the traceback. A cancelled task has neither, and is not counted in tasks_failed_total.
On lock contention, a task.retry_scheduled event (task.retry.reason=lock_contention) is added.
Logs
The log records of the DiracX and extension loggers (and of the uvicorn ones in the API servers) are exported once each, with the context attributes (task, user...) as attributes:
| Field | Content |
|---|---|
| Body | The message |
| Severity | The level of the record |
| Trace and span IDs | Those of the active span, if any |
| Scope name | The name of the logger, e.g. diracx.tasks.plumbing.worker.worker |
code.file.path, code.function.name, code.line.number |
Where the record was emitted |
exception.type, exception.message, exception.stacktrace |
If the record has an exception (logger.exception(...)) |
The logs of the other libraries (SQLAlchemy, httpx...) are not exported.
Metrics
The names are given as exported by DiracX (OpenTelemetry) and as they appear in Prometheus with the default translation of the collector.
Unless stated otherwise, the resource attributes are available as labels (service_name, service_instance_id, diracx_component...) only if the collector converts them (resource_to_telemetry_conversion), as done by the DiracX chart.
HTTP (API servers)
From the FastAPI instrumentation, following the HTTP metrics semantic conventions.
The api/health/ routes are not measured.
| Prometheus name | Type | Labels | Description |
|---|---|---|---|
http_server_request_duration_seconds |
histogram | http_request_method, http_route, http_response_status_code, url_scheme, network_protocol_version |
Duration of the requests |
http_server_active_requests |
gauge | http_request_method, url_scheme |
Requests being served |
http_server_request_body_size_bytes |
histogram | as for the duration | Size of the request bodies |
http_server_response_body_size_bytes |
histogram | as for the duration | Size of the response bodies |
Requests per client version, from DiracX:
| Prometheus name | Type | Labels | Description |
|---|---|---|---|
client_requests_total |
counter | client_version, outcome |
Requests per version of the DiracX client (DiracX-Client-Version header). client_version is the normalised version, none without header (web interface, direct HTTP requests), invalid if it cannot be parsed, and other once 50 distinct versions have been seen by the process (the header is set by the client, so the number of versions is bounded). outcome: accepted, or rejected if the client is older than the minimum supported version or sent an invalid version |
Tasks
task_name, priority and size are the name of the task (e.g. jobs:CleanSandboxStoreTask), its priority and its size.
| Prometheus name | Type | Labels | Emitted by | Description |
|---|---|---|---|---|
tasks_submitted_total |
counter | task_name, priority, size, delayed |
whoever submits | Tasks submitted |
tasks_completed_total |
counter | task_name, priority, size |
worker | Tasks completed successfully |
tasks_failed_total |
counter | task_name, priority, size |
worker | Executions which raised an exception, except when cancelled |
task_duration_seconds |
histogram | task_name, priority, size |
worker | Execution time (not recorded on lock contention). Buckets from 10 ms to 1 h |
task_queue_wait_seconds |
histogram | task_name, priority, size |
worker | Time spent in the stream before being picked up (clock of the Redis server at enqueue, clock of the worker at pick up). Buckets from 10 ms to 1 h |
tasks_in_progress |
gauge | task_name, priority, size |
worker | Tasks being processed |
worker_max_concurrent_tasks |
gauge | worker_size |
worker | Capacity of the worker (--max-concurrent-tasks) |
tasks_retried_total |
counter | task_name, priority, size, reason |
worker | Tasks rescheduled. reason: error, lock_contention |
tasks_given_up_total |
counter | task_name, priority, size, action |
worker | Tasks which exhausted their retries. action: dlq (persisted in the dead letter queue), discarded (not dlq-eligible), dlq_failed (could not be persisted: lost) |
task_messages_rejected_total |
counter | reason, task_name |
worker | Messages dropped. reason: unparsable, unknown_task (the task is not installed on the worker) |
task_messages_reclaimed_total |
counter | stream |
worker | Messages taken over from a worker which did not acknowledge them in time, typically because it died |
task_stream_length |
gauge | stream, priority, size |
scheduler | Entries in the stream |
task_stream_lag |
gauge | stream, priority, size |
scheduler | Messages not delivered to any worker yet (the backlog). Requires Redis 7 |
task_stream_pending |
gauge | stream, priority, size |
scheduler | Messages delivered to a worker but not acknowledged yet |
delayed_tasks_pending |
gauge | scheduler | Tasks in the delayed ZSET (scheduled tasks and retries) | |
delayed_tasks_promoted_total |
counter | scheduler | Delayed tasks moved to their stream | |
scheduler_periodic_submissions_total |
counter | task_name, outcome |
scheduler | Periodic tasks submitted. outcome: success, failure |
scheduler_leader |
gauge | scheduler | 1 if the instance holds the scheduler lock, 0 otherwise |
|
dead_letter_queue_tasks |
gauge | task_name, status |
scheduler | Tasks in the dead letter queue. status: PENDING (waiting to be resubmitted), DISPATCHED (resubmitted), FAILED. Requires the TaskDB (DIRACX_DB_URL_TASKDB), reported every minute |
The stream, delayed and dead letter queue gauges are only reported by the scheduler holding the lock, every 10 seconds.
SQL databases (all processes)
Following the database metrics semantic conventions.
| Prometheus name | Type | Labels | Description |
|---|---|---|---|
db_client_operation_duration_seconds |
histogram | db_system_name, db_namespace, db_operation_name, error_type (if the query failed) |
Duration of the SQL queries. Buckets from 0.5 ms to 10 s |
db_client_connection_count |
gauge | db_client_connection_pool_name, db_client_connection_state (used, idle) |
Connections in the SQLAlchemy pool |
db_client_connection_max |
gauge | db_client_connection_pool_name |
Capacity of the pool (pool_size + max_overflow) |
db_client_connection_wait_time_seconds |
histogram | db_client_connection_pool_name |
Time spent obtaining a connection from the pool, including the creation of a new connection if needed. Buckets from 0.1 ms to 30 s |
db_client_connection_timeouts_total |
counter | db_client_connection_pool_name |
Connections which could not be obtained before the pool timeout (pool_timeout, 30 s by default): the request or task failed |
The pool name is the name of the database.
Dashboards
The DiracX chart ships Grafana dashboards in diracx/dashboards:
| File | Title | Content |
|---|---|---|
diracx-routers.json |
DiracX Routers | Traffic, errors and latency per route and per instance, SQL databases of the API servers |
diracx-tasks.json |
DiracX Tasks | Throughput, execution time and queue wait, streams backlog, worker utilisation, retries and dead letter queue, scheduler, SQL databases of the workers |
metrics.json |
Metrics | HTTP requests rate, sizes and duration per route |
They use a Prometheus data source, and the service_name, service_instance_id and diracx_component labels.