Skip to content
Draft
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
57 changes: 56 additions & 1 deletion backend/kernelCI_app/queries/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,42 @@ def get_metrics_data(
WHERE r.ranked <= 3 AND n.total_incidents > 0
"""

new_build_issues_query = """
WITH time_rank AS (
SELECT
_timestamp,
origin,
issue_id,
ROW_NUMBER() OVER (PARTITION BY issue_id ORDER BY _timestamp) AS rn
FROM incidents
WHERE build_id IS NOT NULL
),
new_issues AS (
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
)
SELECT
inc.origin,
inc.issue_id,
inc.issue_version,
i.comment,
COUNT(inc.*) AS total
FROM incidents inc
JOIN issues i ON inc.issue_id = i.id AND inc.issue_version = i.version
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
GROUP BY inc.origin, inc.issue_id, inc.issue_version, i.comment
ORDER BY inc.origin, total DESC
"""

lab_summary_query = """
-- get count of tests of each lab and how many builds are related to those tests
SELECT
Expand All @@ -806,7 +842,7 @@ def get_metrics_data(
GROUP BY lab
"""

with ThreadPoolExecutor(max_workers=5) as executor:
with ThreadPoolExecutor(max_workers=6) as executor:
total_objects_result = executor.submit(
query_fetchone_work, query=total_objects_query, params=params
)
Expand All @@ -816,6 +852,9 @@ def get_metrics_data(
build_incidents_result = executor.submit(
query_fetchall_work, query=build_incidents_query, params=params
)
new_build_issues_result = executor.submit(
query_fetchall_work, query=new_build_issues_query, params=params
)
lab_summary_results = executor.submit(
query_fetchall_work, query=lab_summary_query, params=params
)
Expand All @@ -826,12 +865,14 @@ def get_metrics_data(
total_objects_result = total_objects_result.result()
prev_total_objects_result = prev_total_objects_result.result()
build_incidents_result = build_incidents_result.result()
new_build_issues_result = new_build_issues_result.result()
lab_summary_results = lab_summary_results.result()
prev_lab_summary_results = prev_lab_summary_results.result()

try:
build_incidents_by_origin: dict[str, BuildIncidentsCount] = {}
top_issues_by_origin: dict[str, dict[tuple[str, int], TopIssue]] = {}
new_issues_by_origin: dict[str, dict[tuple[str, int], TopIssue]] = {}
for row in build_incidents_result:
origin = row[0]
issue_id = row[4]
Expand All @@ -851,6 +892,19 @@ def get_metrics_data(
total_incidents=row[7],
)

for row in new_build_issues_result:
origin = row[0]
issue_id = row[1]
issue_version = row[2]
if new_issues_by_origin.get(origin) is None:
new_issues_by_origin[origin] = {}
new_issues_by_origin[origin][(issue_id, issue_version)] = TopIssue(
id=issue_id,
version=issue_version,
comment=row[3],
total_incidents=row[4],
)

data = MetricsReportData(
n_trees=total_objects_result[0],
n_checkouts=total_objects_result[1],
Expand All @@ -860,6 +914,7 @@ def get_metrics_data(
n_incidents=total_objects_result[5],
build_incidents_by_origin=build_incidents_by_origin,
top_issues_by_origin=top_issues_by_origin,
new_issues_by_origin=new_issues_by_origin,
lab_maps={
row[0]: LabMetricsData(
builds=row[1],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"n_incidents",
"build_incidents_by_origin",
"top_issues_by_origin",
"new_issues_by_origin",
"lab_maps",
"prev_n_trees",
"prev_n_checkouts",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ def make_metrics_data(**overrides) -> MetricsReportData:
),
},
},
new_issues_by_origin={
"maestro": {
("issue-new", 1): TopIssue(
id="issue-new",
version=1,
comment="New regression issue",
total_incidents=10,
),
},
},
lab_maps={
"lava-collabora": LabMetricsData(
builds=0, boots=50000, tests=450000, origin="maestro"
Expand Down
5 changes: 5 additions & 0 deletions backend/kernelCI_app/typeModels/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class MetricsResponse(BaseModel):
n_incidents: int
build_incidents_by_origin: dict[str, BuildIncidentsCount]
top_issues_by_origin: dict[str, list[TopIssue]]
new_issues_by_origin: dict[str, list[TopIssue]]
lab_maps: dict[str, LabMetricsData]
prev_n_trees: int
prev_n_checkouts: int
Expand All @@ -55,6 +56,10 @@ def metrics_report_data_to_response(data: MetricsReportData) -> MetricsResponse:
origin: list(issues.values())
for origin, issues in data.top_issues_by_origin.items()
},
new_issues_by_origin={
origin: list(issues.values())
for origin, issues in data.new_issues_by_origin.items()
},
lab_maps=data.lab_maps,
prev_n_trees=data.prev_n_trees,
prev_n_checkouts=data.prev_n_checkouts,
Expand Down
2 changes: 2 additions & 0 deletions backend/kernelCI_app/typeModels/metrics_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class MetricsReportData(BaseModel):
build_incidents_by_origin: dict[str, BuildIncidentsCount]
# top_issues = origin -> (issue_id, version) -> TopIssue
top_issues_by_origin: dict[str, dict[tuple[str, int], TopIssue]]
# new_issues = origin -> (issue_id, version) -> TopIssue (first build incident in period)
new_issues_by_origin: dict[str, dict[tuple[str, int], TopIssue]]
lab_maps: dict[str, LabMetricsData]
# Previous interval (for comparison)
prev_n_trees: int
Expand Down
39 changes: 39 additions & 0 deletions dashboard/src/api/metrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useQuery, type UseQueryResult } from '@tanstack/react-query';

import type { MetricsResponse } from '@/types/metrics';

import { RequestData } from './commonRequest';

type FetchMetricsParams = {
startDaysAgo: number;
endDaysAgo: number;
};

export const fetchMetrics = async ({
startDaysAgo,
endDaysAgo,
}: FetchMetricsParams): Promise<MetricsResponse> => {
const data = await RequestData.get<MetricsResponse>('/api/metrics/', {
params: {
start_days_ago: startDaysAgo,
end_days_ago: endDaysAgo,
},
});

return data;
};

export const useMetrics = ({
intervalInDays,
}: {
intervalInDays: number;
}): UseQueryResult<MetricsResponse> => {
return useQuery({
queryKey: ['metrics', intervalInDays],
queryFn: () =>
fetchMetrics({
startDaysAgo: intervalInDays,
endDaysAgo: 0,
}),
});
};
9 changes: 8 additions & 1 deletion dashboard/src/components/SideMenu/menuItems.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { JSX } from 'react';

import { MdOutlineMonitorHeart } from 'react-icons/md';
import { MdOutlineBarChart, MdOutlineMonitorHeart } from 'react-icons/md';
import { RxRadiobutton } from 'react-icons/rx';
import { ImTree } from 'react-icons/im';
import { HiOutlineDocumentSearch } from 'react-icons/hi';
Expand Down Expand Up @@ -30,6 +30,7 @@ export type LinkStringItems = {
const TreeIcon = <ImTree className="size-5" />;
const MonitorHeartIcon = <MdOutlineMonitorHeart className="size-5" />;
const RadioButtonIcon = <RxRadiobutton className="size-5" />;
const MetricsIcon = <MdOutlineBarChart className="size-5" />;
const DocumentSearchIcon = <HiOutlineDocumentSearch />;

export const routeItems: RouteMenuItems[] = [
Expand All @@ -51,6 +52,12 @@ export const routeItems: RouteMenuItems[] = [
icon: RadioButtonIcon,
selected: false,
},
{
navigateTo: '/metrics',
idIntl: 'routes.metricsMonitor',
icon: MetricsIcon,
selected: false,
},
];

export const linkItems: LinkMenuItems[] = [
Expand Down
2 changes: 2 additions & 0 deletions dashboard/src/components/TopBar/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ const TitleName = ({ basePath }: { basePath: string }): JSX.Element => {
return <FormattedMessage id="routes.testDetails" />;
case 'issue':
return <FormattedMessage id="routes.issueDetails" />;
case 'metrics':
return <FormattedMessage id="routes.metricsMonitor" />;
default:
return <FormattedMessage id="routes.unknown" />;
}
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/locales/messages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ export const messages = {
'routes.hardwareNewMonitor': 'Hardware New',
'routes.issueDetails': 'Issue',
'routes.issueMonitor': 'Issues',
'routes.metricsMonitor': 'Metrics',
'routes.testDetails': 'Test',
'routes.treeMonitor': 'Trees',
'routes.unknown': 'Unknown',
Expand Down
Loading