Last active
November 1, 2023 17:24
-
-
Save cnicodeme/bdb5502b36a38fcb475dc940627677e1 to your computer and use it in GitHub Desktop.
Generate statistics for Cloudwatch based on the number of SpamAssassin Childs activity
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
# -*- config:utf-8 -*- | |
import boto3, asyncio, sys, os, datetime | |
def get_sa_max_children(default=12): | |
try: | |
options = None | |
with open('/etc/default/spamassassin', 'r') as f: | |
for line in f: | |
if line.find('OPTIONS=') == 0: | |
options = line.strip() | |
break | |
args = iter(options[9:].split(' ')) | |
for arg in args: | |
if arg[0:4] == '--max-children=': | |
return int(arg[15:]) | |
elif arg == '-m': | |
return int(next(args, None)) | |
except Exception: | |
pass | |
return default | |
def get_activity(idles): | |
block = 1024 | |
log_file = '/var/log/mail.info' | |
file_size = os.stat(log_file)[6] | |
start_position = None | |
with open(log_file, 'r') as f: | |
f.seek(file_size, 0) # Go to end | |
data = '' | |
for i in range(1, 100): | |
# We go up until we find the pattern | |
f.seek((file_size - (block * i)), 0) # we go back {block} from the current position | |
part = f.read(block) | |
data = part + data | |
start_position = data.rfind('prefork: child states:') | |
if start_position > 60: | |
# This is to ensure the position is after the last line | |
break | |
if start_position is None: | |
return None, None | |
state = None | |
for line in data.split('\n')[::-1]: | |
if line.find('prefork: child states:') > -1: | |
before, after = line.split('prefork: child states:', 1) | |
parsed_date = None | |
try: | |
# Trying date format 'Wed Nov 1 17:10:39 2023' | |
dt = before[0:before.find(' [')].strip() | |
parsed_date = datetime.datetime.strptime(dt, "%a %b %d %H:%M:%S %Y") | |
except ValueError: | |
# Might be 'Nov 1 17:11:44' | |
dt = before[0:before.find(' [')].strip() | |
dt = dt[0:dt.rfind(' ')] # remove spamd[xxx] | |
dt = dt[0:dt.rfind(' ')] # remove ip-XXX | |
try: | |
parsed_date = datetime.datetime.strptime(dt, "%b %d %H:%M:%S") | |
parsed_date = parsed_date.replace(year=datetime.datetime.now().year) | |
except ValueError: | |
pass | |
if parsed_date and parsed_date < datetime.datetime.now() - datetime.timedelta(minutes=15): | |
return None, None | |
state = after.strip() | |
break | |
busy = 0 | |
for child in list(state): | |
if child == 'B': | |
busy += 1 | |
idles -= 1 | |
activity = 100 if busy > idles else (busy / idles) * 100 | |
print('') | |
print('Idles: {}'.format(idles)) | |
print('Busy: {}'.format(busy)) | |
print('Activity: {}%'.format(activity)) | |
return idles, busy, activity | |
async def main(debug=False, is_delayed=False): | |
default_idles = get_sa_max_children() | |
if debug: | |
return get_activity(default_idles) | |
for i in range(0, 5): | |
idles, busy, activity = get_activity(default_idles) | |
try: | |
assert busy is not None | |
assert activity is not None | |
boto3.client('cloudwatch', region_name='eu-west-3').put_metric_data( | |
Namespace='SpamAssassin Delayed statistics' if is_delayed else 'SpamAssassin statistics', | |
MetricData=[ | |
{ | |
'MetricName': 'Idle', | |
'Unit': 'Count', | |
'Value': idles, | |
'StorageResolution': 1, | |
'Timestamp': datetime.datetime.utcnow() | |
}, | |
{ | |
'MetricName': 'Busy', | |
'Unit': 'Count', | |
'Value': busy, | |
'StorageResolution': 1, | |
'Timestamp': datetime.datetime.utcnow() | |
}, | |
{ | |
'MetricName': 'Activity', | |
'Unit': 'Percent', | |
'Value': activity, | |
'StorageResolution': 1, | |
'Timestamp': datetime.datetime.utcnow() | |
}, | |
] | |
) | |
except Exception: | |
# An error occured | |
pass | |
await asyncio.sleep(10) # We wait 10 seconds | |
if __name__ == '__main__': | |
is_debug = '--debug' in sys.argv | |
is_delayed = '--delayed' in sys.argv | |
asyncio.get_event_loop().run_until_complete(main(is_debug, is_delayed)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment