Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Fabio Todaro committed Jan 31, 2019
0 parents commit b1845ab
Show file tree
Hide file tree
Showing 23 changed files with 811 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[run]
source = celery_exporter

[report]
fail_under = 100
show_missing = True

[paths]
source = celery_exporter
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*
!setup.py
!README.md
!celery_exporter
!requirements
__pycache__
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
*.img
/dist
/build
/*.egg-info

*.pyc
__pycache__
.coverage
.tox/
.cache/
.python-version
.pytest_cache

.vscode
10 changes: 10 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
sudo: false
language: python

python:
- "3.4"
- "3.5"
- "3.6"

install: pip install tox-travis tox
script: tox
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Any PR is welcome!
30 changes: 30 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
FROM python:3.6-alpine
LABEL maintainer="Fabio Todaro <ft@ovalmoney.com>"

ARG BUILD_DATE
ARG DOCKER_REPO
ARG VERSION
ARG VCS_REF

LABEL org.label-schema.schema-version="1.0" \
org.label-schema.build-date=$BUILD_DATE \
org.label-schema.name=$DOCKER_REPO \
org.label-schema.version=$VERSION \
org.label-schema.description="Prometheus metrics exporter for Celery" \
org.label-schema.vcs-ref=$VCS_REF \
org.label-schema.vcs-url="https://github.com/OvalMoney/celery-exporter"

WORKDIR /app/

COPY requirements/ ./requirements

RUN pip install -r ./requirements/requirements.txt

COPY setup.py README.md ./
COPY celery_exporter/ ./celery_exporter/
RUN pip install --no-deps -e .

ENTRYPOINT ["celery-exporter"]
CMD []

EXPOSE 9540
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2019, Ovalmoney Ltd

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include README.md
include LICENSE
38 changes: 38 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
.PHONY: help
.DEFAULT_GOAL := help

DOCKER_REPO="ovalmoney/celery-exporter"
DOCKER_VERSION="latest"

define PRINT_HELP_PYSCRIPT
import re, sys

for line in sys.stdin:
match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line)
if match:
target, help = match.groups()
print("%-20s %s" % (target, help))
endef
export PRINT_HELP_PYSCRIPT

all: clean docker_build ## Clean and Build

clean: ## Clean folders
rm -rf dist/ *.egg-info

docker_build: ## Build Docker file
export DOCKER_REPO
export DOCKER_VERSION

docker build \
--build-arg DOCKER_REPO=${DOCKER_REPO} \
--build-arg VERSION=${DOCKER_VERSION} \
--build-arg VCS_REF=`git rev-parse --short HEAD` \
--build-arg BUILD_DATE=`date -u +”%Y-%m-%dT%H:%M:%SZ”` \
-f ./Dockerfile \
-t ${DOCKER_REPO}:${DOCKER_VERSION} \
.

help: ## Print this help
@python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)

162 changes: 162 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Celery Exporter

![https://hub.docker.com/r/ovalmoney/celery-exporter/](https://img.shields.io/docker/automated/ovalmoney/celery-exporter.svg?maxAge=2592000)

Celery Exporter is a Prometheus metrics exporter for Celery 4, written in python.

Here the list of exposed metrics:

* `celery_tasks` exposes the number of tasks currently known to the queue
labeled by `name`, `state` and `namespace`.
* `celery_tasks_runtime_seconds` tracks the number of seconds tasks take
until completed as histogram labeled by `name` and `namespace`
* `celery_task_latency` exposes a histogram of task latency, i.e. the time until
tasks are picked up by a worker
* `celery_workers` exposes the number of currently probably alive workers

---
## Requirements


### Dependencies
The project comes with `redis` lib already installed, you have to install any other dependency in case you are using other brokers.

### Celery app
Celery workers have to be configured to send task-related events:
http://docs.celeryproject.org/en/latest/userguide/configuration.html#worker-send-task-events.

Celery Exporter is able to enable events on your workers (see _Command Options_).

---
## Install and Run

### Manual Setup
```bash
# Install
$ pip install celery-exporter

# Run
$ celery-exporter
```

### Docker
```bash
# Download image
$ docker pull ovalmoney/celery-exporter

# Run it
$ docker run -it --rm ovalmoney/celery-exporter
```

### Command Options

```bash
$ celery-exporter --help
Usage: celery-exporter [OPTIONS]

Options:
-b, --broker-url TEXT URL to the Celery broker. [env var:
CELERY_EXPORTER_BROKER_URL; default:
redis://redis:6379/0]
-l, --listen-address TEXT Address the HTTPD should listen on. [env var:
CELERY_EXPORTER_LISTEN_ADDRESS; default:
0.0.0.0:9540]
-m, --max-tasks INTEGER Tasks cache size. [env var:
CELERY_EXPORTER_MAX_TASKS; default: 10000]
-n, --namespace TEXT Namespace for metrics. [env var:
CELERY_EXPORTER_NAMESPACE; default: celery]
--transport-options TEXT JSON object with additional options passed to the
underlying transport.
--enable-events Periodically enable Celery events.
--tz TEXT Timezone used by the celery app.
--verbose Enable verbose logging.
--version Show the version and exit.
--help Show this message and exit.
```


If you then look at the exposed metrics, you should see something like this:
```bash
# HELP celery_workers Number of alive workers
# TYPE celery_workers gauge
celery_workers{namespace="celery"} 1.0
# HELP celery_tasks Number of tasks per state
# TYPE celery_tasks gauge
celery_tasks{name="my_app.tasks.calculate_something",namespace="celery",state="RECEIVED"} 0.0
celery_tasks{name="my_app.tasks.calculate_something",namespace="celery",state="PENDING"} 0.0
celery_tasks{name="my_app.tasks.calculate_something",namespace="celery",state="STARTED"} 0.0
celery_tasks{name="my_app.tasks.calculate_something",namespace="celery",state="RETRY"} 0.0
celery_tasks{name="my_app.tasks.calculate_something",namespace="celery",state="FAILURE"} 0.0
celery_tasks{name="my_app.tasks.calculate_something",namespace="celery",state="REVOKED"} 0.0
celery_tasks{name="my_app.tasks.calculate_something",namespace="celery",state="SUCCESS"} 1.0
celery_tasks{name="my_app.tasks.fetch_some_data",namespace="celery",state="RECEIVED"} 3.0
celery_tasks{name="my_app.tasks.fetch_some_data",namespace="celery",state="PENDING"} 0.0
celery_tasks{name="my_app.tasks.fetch_some_data",namespace="celery",state="STARTED"} 1.0
celery_tasks{name="my_app.tasks.fetch_some_data",namespace="celery",state="RETRY"} 2.0
celery_tasks{name="my_app.tasks.fetch_some_data",namespace="celery",state="FAILURE"} 1.0
celery_tasks{name="my_app.tasks.fetch_some_data",namespace="celery",state="REVOKED"} 0.0
celery_tasks{name="my_app.tasks.fetch_some_data",namespace="celery",state="SUCCESS"} 7.0
# HELP celery_tasks_runtime_seconds Task runtime (seconds)
# TYPE celery_tasks_runtime_seconds histogram
celery_tasks_runtime_seconds_bucket{le="0.005",name="my_app.tasks.calculate_something",namespace="celery"} 29.0
celery_tasks_runtime_seconds_bucket{le="0.01",name="my_app.tasks.calculate_something",namespace="celery"} 29.0
celery_tasks_runtime_seconds_bucket{le="0.025",name="my_app.tasks.calculate_something",namespace="celery"} 29.0
celery_tasks_runtime_seconds_bucket{le="0.05",name="my_app.tasks.calculate_something",namespace="celery"} 29.0
celery_tasks_runtime_seconds_bucket{le="0.075",name="my_app.tasks.calculate_something",namespace="celery"} 29.0
celery_tasks_runtime_seconds_bucket{le="0.1",name="my_app.tasks.calculate_something",namespace="celery"} 29.0
celery_tasks_runtime_seconds_bucket{le="0.25",name="my_app.tasks.calculate_something",namespace="celery"} 29.0
celery_tasks_runtime_seconds_bucket{le="0.5",name="my_app.tasks.calculate_something",namespace="celery"} 29.0
celery_tasks_runtime_seconds_bucket{le="0.75",name="my_app.tasks.calculate_something",namespace="celery"} 29.0
celery_tasks_runtime_seconds_bucket{le="1.0",name="my_app.tasks.calculate_something",namespace="celery"} 29.0
celery_tasks_runtime_seconds_bucket{le="2.5",name="my_app.tasks.calculate_something",namespace="celery"} 29.0
celery_tasks_runtime_seconds_bucket{le="5.0",name="my_app.tasks.calculate_something",namespace="celery"} 29.0
celery_tasks_runtime_seconds_bucket{le="7.5",name="my_app.tasks.calculate_something",namespace="celery"} 29.0
celery_tasks_runtime_seconds_bucket{le="10.0",name="my_app.tasks.calculate_something",namespace="celery"} 29.0
celery_tasks_runtime_seconds_bucket{le="+Inf",name="my_app.tasks.calculate_something",namespace="celery"} 29.0
celery_tasks_runtime_seconds_count{name="my_app.tasks.calculate_something",namespace="celery"} 29.0
celery_tasks_runtime_seconds_sum{name="my_app.tasks.calculate_something",namespace="celery"} 0.04020289977779612
celery_tasks_runtime_seconds_bucket{le="0.005",name="my_app.tasks.fetch_some_data",namespace="celery"} 2.0
celery_tasks_runtime_seconds_bucket{le="0.01",name="my_app.tasks.fetch_some_data",namespace="celery"} 2.0
celery_tasks_runtime_seconds_bucket{le="0.025",name="my_app.tasks.fetch_some_data",namespace="celery"} 2.0
celery_tasks_runtime_seconds_bucket{le="0.05",name="my_app.tasks.fetch_some_data",namespace="celery"} 2.0
celery_tasks_runtime_seconds_bucket{le="0.075",name="my_app.tasks.fetch_some_data",namespace="celery"} 2.0
celery_tasks_runtime_seconds_bucket{le="0.1",name="my_app.tasks.fetch_some_data",namespace="celery"} 2.0
celery_tasks_runtime_seconds_bucket{le="0.25",name="my_app.tasks.fetch_some_data",namespace="celery"} 2.0
celery_tasks_runtime_seconds_bucket{le="0.5",name="my_app.tasks.fetch_some_data",namespace="celery"} 2.0
celery_tasks_runtime_seconds_bucket{le="0.75",name="my_app.tasks.fetch_some_data",namespace="celery"} 2.0
celery_tasks_runtime_seconds_bucket{le="1.0",name="my_app.tasks.fetch_some_data",namespace="celery"} 2.0
celery_tasks_runtime_seconds_bucket{le="2.5",name="my_app.tasks.fetch_some_data",namespace="celery"} 2.0
celery_tasks_runtime_seconds_bucket{le="5.0",name="my_app.tasks.fetch_some_data",namespace="celery"} 2.0
celery_tasks_runtime_seconds_bucket{le="7.5",name="my_app.tasks.fetch_some_data",namespace="celery"} 2.0
celery_tasks_runtime_seconds_bucket{le="10.0",name="my_app.tasks.fetch_some_data",namespace="celery"} 2.0
celery_tasks_runtime_seconds_bucket{le="+Inf",name="my_app.tasks.fetch_some_data",namespace="celery"} 2.0
celery_tasks_runtime_seconds_count{name="my_app.tasks.fetch_some_data",namespace="celery"} 2.0
celery_tasks_runtime_seconds_sum{name="my_app.tasks.fetch_some_data",namespace="celery"} 0.00402028997777961
# TYPE celery_tasks_runtime_seconds_created gauge
celery_tasks_runtime_seconds_created{name="my_app.tasks.calculate_something",namespace="celery"} 1.548944949810905e+09
celery_tasks_runtime_seconds_created{name="my_app.tasks.fetch_some_data",namespace="celery"} 1.5489449550243628e+09
# HELP celery_task_latency Seconds between a task is received and started.
# TYPE celery_task_latency histogram
celery_task_latency_bucket{namespace="celery",le="0.005"} 2.0
celery_task_latency_bucket{namespace="celery",le="0.01"} 3.0
celery_task_latency_bucket{namespace="celery",le="0.025"} 4.0
celery_task_latency_bucket{namespace="celery",le="0.05"} 4.0
celery_task_latency_bucket{namespace="celery",le="0.075"} 5.0
celery_task_latency_bucket{namespace="celery",le="0.1"} 5.0
celery_task_latency_bucket{namespace="celery",le="0.25"} 5.0
celery_task_latency_bucket{namespace="celery",le="0.5"} 5.0
celery_task_latency_bucket{namespace="celery",le="0.75"} 5.0
celery_task_latency_bucket{namespace="celery",le="1.0"} 5.0
celery_task_latency_bucket{namespace="celery",le="2.5"} 8.0
celery_task_latency_bucket{namespace="celery",le="5.0"} 11.0
celery_task_latency_bucket{namespace="celery",le="7.5"} 11.0
celery_task_latency_bucket{namespace="celery",le="10.0"} 11.0
celery_task_latency_bucket{namespace="celery",le="+Inf"} 11.0
celery_task_latency_count{namespace="celery"} 11.0
celery_task_latency_sum{namespace="celery"} 16.478713035583496
# TYPE celery_task_latency_created gauge
celery_task_latency_created{namespace="celery"} 1.5489449475378375e+09
```

### Inspired by @zerok work
https://github.com/zerok/celery-prometheus-exporter
Empty file added celery_exporter/__init__.py
Empty file.
75 changes: 75 additions & 0 deletions celery_exporter/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import json
import logging
import os
import signal
import sys
import time

import click
from .core import CeleryExporter

__VERSION__ = (2, 0, 0)

LOG_FORMAT = '[%(asctime)s] %(name)s:%(levelname)s: %(message)s'

@click.command(context_settings={
'auto_envvar_prefix':'CELERY_EXPORTER'
})
@click.option('--broker-url', '-b', type=str, show_default=True, show_envvar=True,
default='redis://redis:6379/0',
help='URL to the Celery broker.')
@click.option('--listen-address', '-l', type=str, show_default=True, show_envvar=True,
default='0.0.0.0:9540',
help='Address the HTTPD should listen on.')
@click.option('--max-tasks', '-m', type=int, show_default=True, show_envvar=True,
default='10000',
help='Tasks cache size.')
@click.option('--namespace', '-n', type=str, show_default=True, show_envvar=True,
default='celery',
help='Namespace for metrics.')
@click.option('--transport-options', type=str, allow_from_autoenv=False,
help='JSON object with additional options passed to the underlying transport.')
@click.option('--enable-events', is_flag=True, allow_from_autoenv=False,
help='Periodically enable Celery events.')
@click.option('--tz', type=str, allow_from_autoenv=False,
help='Timezone used by the celery app.')
@click.option('--verbose', is_flag=True, allow_from_autoenv=False,
help='Enable verbose logging.')
@click.version_option(version='.'.join([str(x) for x in __VERSION__]))
def main(broker_url, listen_address, max_tasks, namespace, transport_options, enable_events, tz, verbose): # pragma: no cover

if verbose:
logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT)
else:
logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)

if tz:
os.environ['TZ'] = tz
time.tzset()

if transport_options:
try:
transport_options = json.loads(transport_options)
except ValueError:
print("Error parsing broker transport options from JSON '{}'"
.format(transport_options), file=sys.stderr)
sys.exit(1)

celery_exporter = CeleryExporter(broker_url, listen_address, max_tasks, namespace, transport_options, enable_events)
celery_exporter.start()

def shutdown(signum, frame): # pragma: no cover
"""
Shutdown is called if the process receives a TERM/INT signal.
"""
logging.info("Shutting down")
sys.exit(0)

signal.signal(signal.SIGINT, shutdown)
signal.signal(signal.SIGTERM, shutdown)

while True:
signal.pause()

if __name__ == '__main__': # pragma: no cover
main() # pylint: disable=E1120
Loading

0 comments on commit b1845ab

Please sign in to comment.