Last active
March 3, 2022 11:04
-
-
Save tabascoterrier/41b7d0b5cca99b45e8d0a63bf02d85b8 to your computer and use it in GitHub Desktop.
A work-in-progress Haraka plugin for www.netdata.cloud
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
# Description: Haraka netdata python.d module | |
# Author: tabascoterrier | |
# SPDX-License-Identifier: GPL-3.0-or-later | |
import psutil | |
from bases.FrameworkServices.SimpleService import SimpleService | |
# TODO: does priority actually do anything? | |
priority = 90000 | |
# Rquires the process_title plugin | |
# Haraka needs to be started with a long enough process title for it to be replaced, e.g. | |
# haraka -c /haraka/ ??????????????????????????????????????????????????????????????????????????????? | |
# To install: | |
# apt install python3-psutil | |
# cp /opt/project/haraka.chart.netdata.py /usr/libexec/netdata/python.d/haraka.chart.py | |
# systemctl restart netdata | |
# To debug: | |
# sudo su -s /bin/bash netdata | |
# /usr/libexec/netdata/plugins.d/python.d.plugin haraka debug trace nolock | |
# cn=3 cc=1 cps=0/0.03/1 rcpts=2/1 rps=0/0.02/1 msgs=2/0.67 mps=0/0.02/1 out=0/0/0 respawn=0 | |
# cn = Total number of connections (inc) | |
# cc = Total number of concurrent connections (abs) | |
# cps = Number of connections in the last second / average / maximum | |
# rcpts = Total number of recipients / Average number of recipients per message | |
# rps = Number of recipients in the last second / average / maximum | |
# msgs = Total number of messages / Average number messages per connection | |
# mps = Number of messages in the last second / average / maximum | |
# out = Mails being processed / Mails waiting to be processed / Mails in temp fail state | |
# respawn = Number of worker processes respawned (only under cluster) | |
# If 'cluster' is used then the master process will show the total across all workers, with the exception of outbound stats. | |
ORDER = [ | |
'connections', | |
'connections_sec', | |
'messages', | |
# 'recipients_msg', | |
# 'recipients_sec', | |
'outbound', | |
'respawn' | |
] | |
# options: [name, title, units, family, context, charttype] | |
CHARTS = { | |
'connections': { | |
'options': [None, 'Connections', 'connections', None, 'haraka.connections', 'line'], | |
'lines': [ | |
['total_connections', 'Total connections', 'incremental'], | |
['concurrent_connections', 'Concurrent connections', 'absolute'] | |
] | |
}, | |
'connections_sec': { | |
'options': [None, 'Connections/sec', 'connections', None, 'haraka.connections_sec', 'line'], | |
'lines': [ | |
['connections_sec_avg', 'Avg connections/sec', 'absolute', None, 1000] | |
] | |
}, | |
'outbound': { | |
'options': [None, 'Outbound', 'messages', None, 'haraka.outbound', 'line'], | |
'lines': [ | |
['in_process', 'Being processed', 'absolute'], | |
['waiting', 'Waiting to be processed', 'absolute'], | |
['temp_fail', 'Temp fail', 'absolute'] | |
] | |
}, | |
'respawn': { | |
'options': [None, 'Worker processes respawned', 'processes', None, 'haraka.respawn', 'line'], | |
'lines': [ | |
['workers_respawned', 'Worker processes respawned', 'incremental'] | |
] | |
}, | |
'messages': { | |
'options': [None, 'Messages', 'messages', None, 'haraka.messages', 'line'], | |
'lines': [ | |
['messages_per_sec_last', 'Last second', 'absolute', None, 1000], | |
['messages_per_sec_avg', 'Average/sec', 'absolute', None, 1000], | |
['messages_per_connection', 'Per Connection', 'absolute', None, 1000] | |
] | |
}, | |
} | |
class Service(SimpleService): | |
def __init__(self, configuration=None, name=None): | |
SimpleService.__init__(self, configuration=configuration, name=name) | |
self.order = ORDER | |
self.definitions = CHARTS | |
# self.num_lines = self.configuration.get('num_lines', 4) | |
@staticmethod | |
def check(): | |
return True | |
def get_data(self): | |
data = self.get_raw_data() | |
if not data: | |
return None | |
return { | |
'total_connections': data.get('cn', 0), | |
'concurrent_connections': data.get('cc', 0), | |
'connections_sec_avg': self.float_int(data['cps'][1]), | |
'in_process': data['out'][0], | |
'waiting': data['out'][1], | |
'temp_fail': data['out'][2], | |
'workers_respawned': data.get('respawn', 0), | |
'messages_per_sec_last': self.float_int(data['mps'][0]), | |
'messages_per_sec_avg': self.float_int(data['mps'][1]), | |
'messages_per_connection': self.float_int(data['msgs'][1]) | |
} | |
def get_raw_data(self): | |
data = {} | |
# Haraka (master) cn=18 cc=0 cps=0/0/1 rcpts=13/1 rps=0/0/1 msgs=13/0.72 mps=0/0/1 out=0/0/0 respawn=0 | |
for proc in psutil.process_iter(): | |
if 'Haraka (master)' in proc.name(): | |
cmd_line = proc.cmdline()[0] | |
# Remove 'Haraka (master)' | |
stats = cmd_line[16:].strip() | |
# Split into k=v strings | |
a = stats.split(' ') | |
# Split those strings into lists | |
for b in a: | |
stat = b.split('=') | |
values = stat[1].split('/') | |
# Store the list if there are multiple values, else just the value | |
if len(values) > 1: | |
data[stat[0]] = values | |
else: | |
data[stat[0]] = values[0] | |
return data | |
# We must return ints, so multiple decimal values by 1000 and divide them again in the chart opts | |
def float_int(self, val): | |
if not val: | |
return 0 | |
return int(float(val) * 1000) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment