Skip to content

Instantly share code, notes, and snippets.

@tabascoterrier
Last active March 3, 2022 11:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tabascoterrier/41b7d0b5cca99b45e8d0a63bf02d85b8 to your computer and use it in GitHub Desktop.
Save tabascoterrier/41b7d0b5cca99b45e8d0a63bf02d85b8 to your computer and use it in GitHub Desktop.
A work-in-progress Haraka plugin for www.netdata.cloud
# -*- 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