From 1aa90adac3cb1764fed2de15465e1215a9c956be Mon Sep 17 00:00:00 2001 From: Joel Falknau Date: Sun, 29 Dec 2024 14:51:33 +1000 Subject: [PATCH] Cron Offset Module --- allianceauth/framework/cron.py | 47 +++++++++++++++++++ .../framework/migrations/0001_initial.py | 28 +++++++++++ allianceauth/framework/migrations/__init__.py | 0 allianceauth/framework/models.py | 19 ++++++++ 4 files changed, 94 insertions(+) create mode 100644 allianceauth/framework/cron.py create mode 100644 allianceauth/framework/migrations/0001_initial.py create mode 100644 allianceauth/framework/migrations/__init__.py create mode 100644 allianceauth/framework/models.py diff --git a/allianceauth/framework/cron.py b/allianceauth/framework/cron.py new file mode 100644 index 00000000..044247cf --- /dev/null +++ b/allianceauth/framework/cron.py @@ -0,0 +1,47 @@ +from celery.schedules import crontab +import logging +from allianceauth.framework.models import CronOffset +from django.db import ProgrammingError + +logger = logging.getLogger(__name__) + + +# CELERYBEAT_SCHEDULE['marketmanager_fetch_public_market_orders'] = { +# 'task': 'marketmanager.tasks.fetch_public_market_orders', +# 'schedule': crontab(minute=0, hour='*/3'), +# } + + +def offset_cron(schedule: crontab) -> crontab: + """Take a crontab and apply a series of precalculated offsets to spread out tasks execution on remote resources + + Args: + schedule (crontab): celery.schedules.crontab() + + Returns: + crontab: A crontab with offsetted Minute and Hour fields + """ + + try: + cron_offset = CronOffset.get_solo() + new_minute = [(m + (round(60 * cron_offset.minute))) % 60 for m in schedule.minute] + new_hour = [(m + (round(24 * cron_offset.hour))) % 24 for m in schedule.hour] + + # Type hints are fine here, crontab _expand_cronspec handles sets, the hinting is old + return crontab( + minute=",".join(str(m) for m in sorted(new_minute)), + hour=",".join(str(h) for h in sorted(new_hour)), + day_of_month=schedule._orig_day_of_month, + month_of_year=schedule._orig_month_of_year, + day_of_week=schedule._orig_day_of_week) + + except ProgrammingError as e: + # If this is called before migrations are run hand back the default schedule + # These offsets are stored in a Singleton Model, + logger.error(e) + return schedule + + except Exception as e: + # We absolutely cant fail to hand back a schedule + logger.error(e) + return schedule diff --git a/allianceauth/framework/migrations/0001_initial.py b/allianceauth/framework/migrations/0001_initial.py new file mode 100644 index 00000000..e3fb6d2d --- /dev/null +++ b/allianceauth/framework/migrations/0001_initial.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.16 on 2024-12-29 04:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='CronOffset', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('minute', models.FloatField(default=0.1850723825351236, verbose_name='Minute Offset')), + ('hour', models.FloatField(default=0.3449986529941984, verbose_name='Hour Offset')), + ('day_of_month', models.FloatField(default=0.8941823028373547, verbose_name='Day of Month Offset')), + ('month_of_year', models.FloatField(default=0.6068269517452, verbose_name='Month Of Year Offset')), + ('day_of_week', models.FloatField(default=0.8863389239634608, verbose_name='Day of Week Offset')), + ], + options={ + 'verbose_name': 'Cron Offsets', + }, + ), + ] diff --git a/allianceauth/framework/migrations/__init__.py b/allianceauth/framework/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/allianceauth/framework/models.py b/allianceauth/framework/models.py new file mode 100644 index 00000000..287a13de --- /dev/null +++ b/allianceauth/framework/models.py @@ -0,0 +1,19 @@ +from random import random +from django.db import models +from django.utils.translation import gettext_lazy as _ +from solo.models import SingletonModel + + +class CronOffset(SingletonModel): + + minute = models.FloatField(_("Minute Offset"), default=random()) + hour = models.FloatField(_("Hour Offset"), default=random()) + day_of_month = models.FloatField(_("Day of Month Offset"), default=random()) + month_of_year = models.FloatField(_("Month Of Year Offset"), default=random()) + day_of_week = models.FloatField(_("Day of Week Offset"), default=random()) + + def __str__(self) -> str: + return "Cron Offsets" + + class Meta: + verbose_name = "Cron Offsets"