From b1ce5b3e84e0732b6666f7821ac70074e08e74c6 Mon Sep 17 00:00:00 2001 From: Julian Rother Date: Sun, 9 Feb 2025 02:01:45 +0100 Subject: [PATCH] Prometheus metrics for Dovecot --- handlers/main.yml | 7 +++ tasks/main.yml | 14 ++++++ templates/dovecot/dovecot.conf.j2 | 26 ++++++++++ .../prometheus-dovecot-master-exporter.j2 | 47 +++++++++++++++++++ ...metheus-dovecot-master-exporter.service.j2 | 12 +++++ 5 files changed, 106 insertions(+) create mode 100644 templates/dovecot/prometheus-dovecot-master-exporter.j2 create mode 100644 templates/dovecot/prometheus-dovecot-master-exporter.service.j2 diff --git a/handlers/main.yml b/handlers/main.yml index 90ae1ae..2987547 100644 --- a/handlers/main.yml +++ b/handlers/main.yml @@ -1,6 +1,13 @@ - name: restart dovecot service: name=dovecot state=restarted +- name: restart prometheus-dovecot-master-exporter + ansible.builtin.systemd_service: + name: prometheus-dovecot-master-exporter + state: restarted + enabled: true + daemon_reload: true + - name: restart postfix service: name=postfix state=restarted diff --git a/tasks/main.yml b/tasks/main.yml index be2bb74..4c95631 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -127,6 +127,20 @@ ansible.builtin.shell: "sievec '{{ item.dest }}'" loop: "{{ mailserver_sieve_scripts.results }}" +- name: copy prometheus-dovecot-master-exporter + ansible.builtin.template: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + mode: "{{ item.mode }}" + loop: + - src: dovecot/prometheus-dovecot-master-exporter.j2 + dest: /usr/local/sbin/prometheus-dovecot-master-exporter + mode: "0755" + - src: dovecot/prometheus-dovecot-master-exporter.service.j2 + dest: /etc/systemd/system/prometheus-dovecot-master-exporter.service + mode: "0644" + notify: restart prometheus-dovecot-master-exporter + # prometheus-postfix-exporter - name: configure prometheus postfix exporter ansible.builtin.template: diff --git a/templates/dovecot/dovecot.conf.j2 b/templates/dovecot/dovecot.conf.j2 index 3b830c8..5c7b490 100644 --- a/templates/dovecot/dovecot.conf.j2 +++ b/templates/dovecot/dovecot.conf.j2 @@ -136,6 +136,7 @@ service quota-status { client_limit = 1 } +# Metrics service stats { unix_listener stats-reader { user = vmail @@ -149,6 +150,31 @@ service stats { # 0666 instead of 0660, so postfixadmin can call doveadm pw without errors mode = 0666 } + + inet_listener http { + port = 9900 + } +} + +metric auths { + filter = event=auth_request_finished +} +metric auth_successes { + filter = event=auth_request_finished AND success=yes +} + +metric imap_commands { + filter = event=imap_command_finished + group_by = cmd_name tagged_reply_state duration:exponential:1:9:10 +} + +metric sieve_actions { + filter = event=sieve_action_finished + group_by = action_name +} + +metric mail_deliveries { + filter = event=mail_delivery_finished } # Postfix delivers incoming mails via lda (transport "dovecot") diff --git a/templates/dovecot/prometheus-dovecot-master-exporter.j2 b/templates/dovecot/prometheus-dovecot-master-exporter.j2 new file mode 100644 index 0000000..689c995 --- /dev/null +++ b/templates/dovecot/prometheus-dovecot-master-exporter.j2 @@ -0,0 +1,47 @@ +#!/usr/bin/python3 +import time +import subprocess +import json +from prometheus_client import start_http_server, PROCESS_COLLECTOR, PLATFORM_COLLECTOR, GC_COLLECTOR +from prometheus_client.core import GaugeMetricFamily, CounterMetricFamily, REGISTRY +from prometheus_client.registry import Collector + +class CustomCollector(Collector): + def collect(self): + service_status = json.loads(subprocess.run(['doveadm', '-f', 'json', 'service', 'status'], check=True, capture_output=True).stdout.decode()) + process_status = json.loads(subprocess.run(['doveadm', '-f', 'json', 'process', 'status'], check=True, capture_output=True).stdout.decode()) + dovecot_processes_count = GaugeMetricFamily('dovecot_processes_count', 'Number of running processes per service', labels=['service']) + dovecot_processes_total = CounterMetricFamily('dovecot_processes_total', 'Total count of started processes per service', labels=['service']) + dovecot_processes_limit = GaugeMetricFamily('dovecot_processes_limit', 'Process limit per service', labels=['service']) + dovecot_clients_count = GaugeMetricFamily('dovecot_clients_count', 'Number of connected clients per service', labels=['service']) + dovecot_clients_total = CounterMetricFamily('dovecot_clients_total', 'Total count of clients per service', labels=['service']) + dovecot_clients_per_process_limit = GaugeMetricFamily('dovecot_clients_per_process_limit', 'Client limit per process', labels=['service']) + for line in service_status: + dovecot_processes_count.add_metric([line['name']], int(line['process_count'])) + dovecot_processes_total.add_metric([line['name']], int(line['process_total'])) + dovecot_processes_limit.add_metric([line['name']], int(line['process_limit'])) + dovecot_clients_count.add_metric( + [line['name']], + sum(int(line['client_limit'])-int(proc['available_count']) for proc in process_status if proc['name'] == line['name']) + ) + dovecot_clients_total.add_metric( + [line['name']], + sum(int(proc['total_count']) for proc in process_status if proc['name'] == line['name']) + ) + dovecot_clients_per_process_limit.add_metric([line['name']], int(line['client_limit'])) + yield dovecot_processes_count + yield dovecot_processes_total + yield dovecot_processes_limit + yield dovecot_clients_count + yield dovecot_clients_total + yield dovecot_clients_per_process_limit + +REGISTRY.unregister(PROCESS_COLLECTOR) +REGISTRY.unregister(PLATFORM_COLLECTOR) +REGISTRY.unregister(GC_COLLECTOR) +REGISTRY.register(CustomCollector()) + +if __name__ == '__main__': + start_http_server(addr='127.0.0.1', port=9162) + while True: + time.sleep(100) diff --git a/templates/dovecot/prometheus-dovecot-master-exporter.service.j2 b/templates/dovecot/prometheus-dovecot-master-exporter.service.j2 new file mode 100644 index 0000000..00cbc78 --- /dev/null +++ b/templates/dovecot/prometheus-dovecot-master-exporter.service.j2 @@ -0,0 +1,12 @@ +[Unit] +Description=Dovecot master prometheus exporter +After=dovecot.service +Requisite=dovecot.service + +[Install] +WantedBy=multi-user.target + +[Service] +ExecStart=/usr/bin/python3 /usr/local/sbin/prometheus-dovecot-master-exporter +Environment=PYTHONUNBUFFERED=1 +Restart=always