Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions backend/kernelCI/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,10 @@ def get_json_env_var(name, default):
"--monitoring-id=delete_unused_hardware_status",
],
),
(
"10 0 * * 6",
"kernelCI_app.queries.notifications.warm_metrics_cache",
),
(
"0 0 * * 6",
"django.core.management.call_command",
Expand Down
4 changes: 2 additions & 2 deletions backend/kernelCI_app/constants/localization.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ class DocStrings:
)
DEFAULT_INTERVAL_DESCRIPTION = "Interval in days for the listing"
METRICS_START_DAYS_AGO_DESCRIPTION = (
"Number of days ago that marks the start of the metrics interval"
"Inclusive UTC day offset for the start of a [start, end) metrics interval"
)
METRICS_END_DAYS_AGO_DESCRIPTION = (
"Number of days ago that marks the end of the metrics interval"
"Exclusive UTC day offset for the end of a [start, end) metrics interval"
)
DEFAULT_LISTING_STARTING_DATE_DESCRIPTION = (
"Starting date to calculate the search interval."
Expand Down
13 changes: 10 additions & 3 deletions backend/kernelCI_app/management/commands/notifications.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
import sys
from collections import defaultdict
from datetime import datetime, timedelta, timezone
from datetime import datetime, time, timedelta, timezone
from email.utils import make_msgid
from types import SimpleNamespace
from typing import Optional
Expand Down Expand Up @@ -38,6 +38,7 @@
from kernelCI_app.queries.notifications import (
get_checkout_summary_data,
get_metrics_data,
interval_params,
kcidb_build_incidents,
kcidb_issue_details,
kcidb_last_build_without_issue,
Expand Down Expand Up @@ -847,8 +848,14 @@ def generate_metrics_report(
return

now = datetime.now(timezone.utc)
start_datetime = now - timedelta(days=start_days_ago)
end_datetime = now - timedelta(days=end_days_ago)
bounds = interval_params(start_days_ago, end_days_ago)
start_datetime = datetime.fromisoformat(bounds["start_date"])
exclusive_end = datetime.fromisoformat(bounds["end_date"])
end_datetime = datetime.combine(
exclusive_end.date() - timedelta(days=1),
time.max,
tzinfo=timezone.utc,
)

data: MetricsReportData = get_metrics_data(
start_days_ago=start_days_ago,
Expand Down
175 changes: 115 additions & 60 deletions backend/kernelCI_app/queries/notifications.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import sys
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime, time, timedelta, timezone
from typing import Any

from django.db import connection, connections
Expand All @@ -16,7 +17,9 @@
TopIssue,
)

METRICS_CACHE_WARM_PERIODS = (7, 14)
METRICS_CACHE_TIMEOUT = 60 * 60 * 6 # 6 hours
METRICS_CACHE_WARM_TIMEOUT = 60 * 60 * 24 * 8 # 8 days


def kcidb_execute_query(query, params=None):
Expand Down Expand Up @@ -698,17 +701,24 @@ def query_fetchone_work(
query: str,
params: dict[str, Any],
timeout: int = METRICS_CACHE_TIMEOUT,
):
rows = get_query_cache(key=cache_key, params=params)
if rows is not None:
return rows
use_cache: bool = True,
) -> Any:
if use_cache:
cached = get_query_cache(key=cache_key, params=params)
if cached is not None:
return cached
try:
with connections["default"].cursor() as cursor:
cursor.execute(query, params)
rows = cursor.fetchone()
finally:
connections["default"].close()
set_query_cache(key=cache_key, params=params, rows=rows, timeout=timeout)
set_query_cache(
key=cache_key,
params=params,
rows=rows,
timeout=timeout,
)
return rows


Expand All @@ -718,24 +728,50 @@ def query_fetchall_work(
query: str,
params: dict[str, Any],
timeout: int = METRICS_CACHE_TIMEOUT,
):
rows = get_query_cache(key=cache_key, params=params)
if rows is not None:
return rows
use_cache: bool = True,
) -> Any:
if use_cache:
cached = get_query_cache(key=cache_key, params=params)
if cached is not None:
return cached
try:
with connections["default"].cursor() as cursor:
cursor.execute(query, params)
rows = cursor.fetchall()
finally:
connections["default"].close()
set_query_cache(key=cache_key, params=params, rows=rows, timeout=timeout)
set_query_cache(
key=cache_key,
params=params,
rows=rows,
timeout=timeout,
)
return rows


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()
start_datetime = datetime.combine(
today - timedelta(days=start_days_ago), time.min, tzinfo=timezone.utc
)
end_datetime = datetime.combine(
today - timedelta(days=end_days_ago),
time.min,
tzinfo=timezone.utc,
)
return {
"start_date": start_datetime.isoformat(),
"end_date": end_datetime.isoformat(),
}


def get_metrics_data(
*,
start_days_ago: int,
end_days_ago: int,
use_cache: bool = True,
cache_timeout: int = METRICS_CACHE_TIMEOUT,
) -> MetricsReportData:

if start_days_ago > 30:
Expand All @@ -747,41 +783,37 @@ def get_metrics_data(
prev_start_days_ago = start_days_ago - period_length
prev_end_days_ago = start_days_ago

params = {
"start_days_ago": str(start_days_ago) + " days",
"end_days_ago": str(end_days_ago) + " days",
}
prev_params = {
"start_days_ago": str(prev_start_days_ago) + " days",
"end_days_ago": str(prev_end_days_ago) + " days",
}
params = interval_params(start_days_ago, end_days_ago)
prev_params = interval_params(prev_start_days_ago, prev_end_days_ago)

total_objects_query = """
SELECT
(SELECT COUNT(DISTINCT tree_name) FROM checkouts WHERE _timestamp BETWEEN
NOW() - INTERVAL %(start_days_ago)s
AND NOW() - INTERVAL %(end_days_ago)s)
AS n_trees,
(SELECT COUNT(*) FROM checkouts WHERE _timestamp BETWEEN
NOW() - INTERVAL %(start_days_ago)s
AND NOW() - INTERVAL %(end_days_ago)s)
AS n_checkouts,
(SELECT COUNT(*) FROM builds WHERE _timestamp BETWEEN
NOW() - INTERVAL %(start_days_ago)s
AND NOW() - INTERVAL %(end_days_ago)s)
c.n_trees,
c.n_checkouts,
(SELECT COUNT(*) FROM builds WHERE _timestamp >=
%(start_date)s::timestamptz
AND _timestamp < %(end_date)s::timestamptz)
AS n_builds,
(SELECT COUNT(*) FROM tests WHERE _timestamp BETWEEN
NOW() - INTERVAL %(start_days_ago)s
AND NOW() - INTERVAL %(end_days_ago)s)
(SELECT COUNT(*) FROM tests WHERE _timestamp >=
%(start_date)s::timestamptz
AND _timestamp < %(end_date)s::timestamptz)
AS n_tests,
(SELECT COUNT(*) FROM issues WHERE _timestamp BETWEEN
NOW() - INTERVAL %(start_days_ago)s
AND NOW() - INTERVAL %(end_days_ago)s)
(SELECT COUNT(*) FROM issues WHERE _timestamp >=
%(start_date)s::timestamptz
AND _timestamp < %(end_date)s::timestamptz)
AS n_issues,
(SELECT COUNT(*) FROM incidents WHERE _timestamp BETWEEN
NOW() - INTERVAL %(start_days_ago)s
AND NOW() - INTERVAL %(end_days_ago)s)
AS n_incidents;
(SELECT COUNT(*) FROM incidents WHERE _timestamp >=
%(start_date)s::timestamptz
AND _timestamp < %(end_date)s::timestamptz)
AS n_incidents
FROM (
SELECT
COUNT(DISTINCT tree_name) AS n_trees,
COUNT(*) AS n_checkouts
FROM checkouts
WHERE _timestamp >= %(start_date)s::timestamptz
AND _timestamp < %(end_date)s::timestamptz
) c;
"""

build_incidents_query = """
Expand All @@ -800,20 +832,14 @@ def get_metrics_data(
SELECT
origin,
COUNT(*) FILTER (
WHERE _timestamp BETWEEN
NOW() - INTERVAL %(start_days_ago)s
AND NOW() - INTERVAL %(end_days_ago)s
WHERE _timestamp >= %(start_date)s::timestamptz AND _timestamp < %(end_date)s::timestamptz
) AS total_incidents,
COUNT(*) FILTER (
WHERE _timestamp BETWEEN
NOW() - INTERVAL %(start_days_ago)s
AND NOW() - INTERVAL %(end_days_ago)s
WHERE _timestamp >= %(start_date)s::timestamptz AND _timestamp < %(end_date)s::timestamptz
AND rn = 1
) AS n_new_issues,
COUNT(DISTINCT issue_id) FILTER (
WHERE _timestamp BETWEEN
NOW() - INTERVAL %(start_days_ago)s
AND NOW() - INTERVAL %(end_days_ago)s
WHERE _timestamp >= %(start_date)s::timestamptz AND _timestamp < %(end_date)s::timestamptz
) AS n_issues
FROM time_rank
GROUP BY origin
Expand All @@ -830,9 +856,9 @@ def get_metrics_data(
JOIN issues i ON inc.issue_id = i.id AND inc.issue_version = i.version
WHERE
inc.build_id is not null
AND inc._timestamp BETWEEN
NOW() - INTERVAL %(start_days_ago)s
AND NOW() - INTERVAL %(end_days_ago)s
AND inc._timestamp >=
%(start_date)s::timestamptz
AND inc._timestamp < %(end_date)s::timestamptz
GROUP BY inc.origin, inc.issue_id, inc.issue_version, i.comment
ORDER BY inc.origin, total DESC
),
Expand Down Expand Up @@ -874,9 +900,7 @@ def get_metrics_data(
SELECT issue_id, origin
FROM time_rank
WHERE rn = 1
AND _timestamp BETWEEN
NOW() - INTERVAL %(start_days_ago)s
AND NOW() - INTERVAL %(end_days_ago)s
AND _timestamp >= %(start_date)s::timestamptz AND _timestamp < %(end_date)s::timestamptz
)
SELECT
inc.origin,
Expand All @@ -889,9 +913,9 @@ def get_metrics_data(
JOIN new_issues ni ON inc.issue_id = ni.issue_id AND inc.origin = ni.origin
WHERE
inc.build_id IS NOT NULL
AND inc._timestamp BETWEEN
NOW() - INTERVAL %(start_days_ago)s
AND NOW() - INTERVAL %(end_days_ago)s
AND inc._timestamp >=
%(start_date)s::timestamptz
AND inc._timestamp < %(end_date)s::timestamptz
GROUP BY inc.origin, inc.issue_id, inc.issue_version, i.comment
ORDER BY inc.origin, total DESC
"""
Expand All @@ -906,9 +930,9 @@ def get_metrics_data(
FROM tests t
WHERE
t.misc->>'runtime' IS NOT NULL
AND t._timestamp BETWEEN
NOW() - INTERVAL %(start_days_ago)s
AND NOW() - INTERVAL %(end_days_ago)s
AND t._timestamp >=
%(start_date)s::timestamptz
AND t._timestamp < %(end_date)s::timestamptz
GROUP BY lab
"""

Expand All @@ -918,36 +942,48 @@ def get_metrics_data(
cache_key="metricsTotalObjects",
query=total_objects_query,
params=params,
use_cache=use_cache,
timeout=cache_timeout,
)
prev_total_objects_result = executor.submit(
query_fetchone_work,
cache_key="metricsTotalObjects",
query=total_objects_query,
params=prev_params,
use_cache=use_cache,
timeout=cache_timeout,
)
build_incidents_result = executor.submit(
query_fetchall_work,
cache_key="metricsBuildIncidents",
query=build_incidents_query,
params=params,
use_cache=use_cache,
timeout=cache_timeout,
)
new_build_issues_result = executor.submit(
query_fetchall_work,
cache_key="metricsNewBuildIssues",
query=new_build_issues_query,
params=params,
use_cache=use_cache,
timeout=cache_timeout,
)
lab_summary_results = executor.submit(
query_fetchall_work,
cache_key="metricsLabSummary",
query=lab_summary_query,
params=params,
use_cache=use_cache,
timeout=cache_timeout,
)
prev_lab_summary_results = executor.submit(
query_fetchall_work,
cache_key="metricsLabSummary",
query=lab_summary_query,
params=prev_params,
use_cache=use_cache,
timeout=cache_timeout,
)

total_objects_result = total_objects_result.result()
Expand Down Expand Up @@ -1029,3 +1065,22 @@ def get_metrics_data(
raise e

return data


def warm_metrics_cache() -> None:
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)"
)
try:
get_metrics_data(
start_days_ago=period_days,
end_days_ago=0,
use_cache=False,
cache_timeout=METRICS_CACHE_WARM_TIMEOUT,
)
out(f"Warmed metrics cache for {period_days}-day period")
except Exception as e:
out(f"Failed to warm metrics cache for {period_days}-day period: {e}")
4 changes: 2 additions & 2 deletions backend/kernelCI_app/tests/factories/build_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
"""

import factory
from django.utils import timezone
from factory.django import DjangoModelFactory

from kernelCI_app.models import Builds, StatusChoices

from .checkout_factory import CheckoutFactory
from .mocks import Build, Checkout
from .seed_time import seeded_timestamp_for_index


class BuildFactory(DjangoModelFactory):
Expand Down Expand Up @@ -93,4 +93,4 @@ class Meta:
)
)

field_timestamp = factory.LazyFunction(lambda: timezone.now())
field_timestamp = factory.Sequence(seeded_timestamp_for_index)
Loading
Loading