# -*- 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))