diff --git a/backend/kernelCI_app/apps.py b/backend/kernelCI_app/apps.py index e233075fd..98ebd64ff 100644 --- a/backend/kernelCI_app/apps.py +++ b/backend/kernelCI_app/apps.py @@ -1,3 +1,7 @@ +import os +import sys +import threading + from django.apps import AppConfig @@ -6,4 +10,20 @@ class KernelciAppConfig(AppConfig): name = "kernelCI_app" def ready(self) -> None: - return super().ready() + super().ready() + is_server = ( + os.path.basename(sys.argv[0]) == "gunicorn" or "runserver" in sys.argv + ) + if not is_server: + return + if "runserver" in sys.argv and os.environ.get("RUN_MAIN") != "true": + return + + from django.core.cache import cache + + from kernelCI_app.queries.notifications import warm_metrics_cache + + # Single-flight via atomic Redis add: only one worker warms per deploy. + if not cache.add("metrics_startup_warm_lock", 1, timeout=600): + return + threading.Thread(target=warm_metrics_cache, daemon=True).start() diff --git a/backend/kernelCI_app/queries/notifications.py b/backend/kernelCI_app/queries/notifications.py index d9527df2f..bde33f450 100644 --- a/backend/kernelCI_app/queries/notifications.py +++ b/backend/kernelCI_app/queries/notifications.py @@ -1,6 +1,6 @@ import sys from concurrent.futures import ThreadPoolExecutor -from datetime import datetime, time, timedelta, timezone +from datetime import date, datetime, time, timedelta, timezone from typing import Any from django.db import connection, connections @@ -749,6 +749,12 @@ def query_fetchall_work( return rows +def last_saturday(today: date | None = None) -> date: + """Most recent Saturday UTC (today when today is Saturday).""" + day = today or datetime.now(timezone.utc).date() + return day - timedelta(days=(day.weekday() + 2) % 7) + + def interval_params(start_days_ago: int, end_days_ago: int) -> dict[str, str]: """Build [start_date, end_date) bounds from UTC day offsets.""" today = datetime.now(timezone.utc).date() @@ -1068,16 +1074,17 @@ def get_metrics_data( def warm_metrics_cache() -> None: + today = datetime.now(timezone.utc).date() + end_days_ago = (today - last_saturday(today)).days for period_days in METRICS_CACHE_WARM_PERIODS: out( - "Warming metrics cache for " - f"{period_days}-day period " - f"(start_days_ago={period_days}, end_days_ago=0)" + f"Warming metrics cache for {period_days}-day Sat-Fri period " + f"(end_days_ago={end_days_ago})" ) try: get_metrics_data( - start_days_ago=period_days, - end_days_ago=0, + start_days_ago=end_days_ago + period_days, + end_days_ago=end_days_ago, use_cache=False, cache_timeout=METRICS_CACHE_WARM_TIMEOUT, ) diff --git a/backend/kernelCI_app/urls.py b/backend/kernelCI_app/urls.py index 2ae755fad..a89890862 100644 --- a/backend/kernelCI_app/urls.py +++ b/backend/kernelCI_app/urls.py @@ -8,7 +8,6 @@ ) from kernelCI_app import views -from kernelCI_app.queries.notifications import METRICS_CACHE_TIMEOUT def view_cache(view, timeout: int = settings.CACHE_TIMEOUT): @@ -180,9 +179,5 @@ def view_cache(view, timeout: int = settings.CACHE_TIMEOUT): path("proxy/", views.ProxyView.as_view(), name="proxyView"), path("origins/", views.OriginsView.as_view(), name="originsView"), path("tree-report/", views.TreeReport.as_view(), name="treeReportView"), - path( - "metrics/", - view_cache(views.MetricsView, timeout=METRICS_CACHE_TIMEOUT), - name="metricsView", - ), + path("metrics/", views.MetricsView.as_view(), name="metricsView"), ] diff --git a/dashboard/src/pages/Metrics/MetricsPage.tsx b/dashboard/src/pages/Metrics/MetricsPage.tsx index 76b799597..fc2d1f925 100644 --- a/dashboard/src/pages/Metrics/MetricsPage.tsx +++ b/dashboard/src/pages/Metrics/MetricsPage.tsx @@ -551,11 +551,8 @@ function LabsSection({ labs }: { labs: LabData[] }): JSX.Element { const getMetricsQueryParams = ( activeDays: number, ): { startDaysAgo: number; endDaysAgo: number } => { - const today = new Date(); - const weekEndDaysAgo = (today.getUTCDay() + 1) % DAYS_IN_WEEK; - const endDaysAgo = Math.max(weekEndDaysAgo - 1, 0); - const startDaysAgo = endDaysAgo + activeDays; - return { startDaysAgo, endDaysAgo }; + const endDaysAgo = (new Date().getUTCDay() + 1) % DAYS_IN_WEEK; + return { startDaysAgo: endDaysAgo + activeDays, endDaysAgo }; }; export const MetricsPage = (): JSX.Element => {