diff --git a/changes/issue-1892-query-perf-everywhere b/changes/issue-1892-query-perf-everywhere
index 0af13df45..69a233618 100644
--- a/changes/issue-1892-query-perf-everywhere
+++ b/changes/issue-1892-query-perf-everywhere
@@ -1 +1,2 @@
-* Add performance statistics to queries and scheduled queries.
+* Add performance statistics to queries and scheduled queries on backend.
+* Add performance impact column to UI: host details packs tables, schedule tables, edit pack query table, and manage queries table
diff --git a/cypress/integration/all/app/labelflow.spec.ts b/cypress/integration/all/app/labelflow.spec.ts
index 6c760cea5..f7ab6a3ec 100644
--- a/cypress/integration/all/app/labelflow.spec.ts
+++ b/cypress/integration/all/app/labelflow.spec.ts
@@ -65,7 +65,7 @@ describe(
// delete custom label
cy.get(".manage-hosts__label-block button").last().click();
- cy.wait(3000); // eslint-disable-line cypress/no-unnecessary-waiting
+ cy.wait(4000); // eslint-disable-line cypress/no-unnecessary-waiting
cy.get(".manage-hosts__modal-buttons > .button--alert")
.contains("button", /delete/i)
.click();
diff --git a/docs/01-Using-Fleet/03-REST-API.md b/docs/01-Using-Fleet/03-REST-API.md
index 3ebaccec9..b446ab4fd 100644
--- a/docs/01-Using-Fleet/03-REST-API.md
+++ b/docs/01-Using-Fleet/03-REST-API.md
@@ -2317,7 +2317,7 @@ Returns a list of all queries in the Fleet instance.
"system_time_p50": 1.32,
"system_time_p95": 4.02,
"user_time_p50": 3.55,
- "user_time_p_95": 3.00,
+ "user_time_p95": 3.00,
"total_executions": 3920
}
},
@@ -3202,7 +3202,7 @@ None.
"system_time_p50": 1.32,
"system_time_p95": 4.02,
"user_time_p50": 3.55,
- "user_time_p_95": 3.00,
+ "user_time_p95": 3.00,
"total_executions": 3920
}
},
@@ -3226,7 +3226,7 @@ None.
"system_time_p50": 1.32,
"system_time_p95": 4.02,
"user_time_p50": 3.55,
- "user_time_p_95": 3.00,
+ "user_time_p95": 3.00,
"total_executions": 3920
}
}
@@ -3420,7 +3420,7 @@ This allows you to easily configure scheduled queries that will impact a whole t
"system_time_p50": 1.32,
"system_time_p95": 4.02,
"user_time_p50": 3.55,
- "user_time_p_95": 3.00,
+ "user_time_p95": 3.00,
"total_executions": 3920
}
},
@@ -3444,7 +3444,7 @@ This allows you to easily configure scheduled queries that will impact a whole t
"system_time_p50": 1.32,
"system_time_p95": 4.02,
"user_time_p50": 3.55,
- "user_time_p_95": 3.00,
+ "user_time_p95": 3.00,
"total_executions": 3920
}
}
diff --git a/frontend/components/TableContainer/DataTable/PillCell/PillCell.tsx b/frontend/components/TableContainer/DataTable/PillCell/PillCell.tsx
index 9644d51a4..5386edc1a 100644
--- a/frontend/components/TableContainer/DataTable/PillCell/PillCell.tsx
+++ b/frontend/components/TableContainer/DataTable/PillCell/PillCell.tsx
@@ -28,8 +28,6 @@ const PillCell = ({ value, customIdPrefix }: IPillCellProps): JSX.Element => {
return false;
case "Excessive":
return false;
- case "Denylisted":
- return false;
default:
return true;
}
@@ -91,7 +89,7 @@ const PillCell = ({ value, customIdPrefix }: IPillCellProps): JSX.Element => {
>
{tooltipText()}
diff --git a/frontend/components/TableContainer/DataTable/_styles.scss b/frontend/components/TableContainer/DataTable/_styles.scss
index b5eca9c91..7ce850f94 100644
--- a/frontend/components/TableContainer/DataTable/_styles.scss
+++ b/frontend/components/TableContainer/DataTable/_styles.scss
@@ -171,6 +171,27 @@
border-radius: 29px;
background-color: $core-fleet-purple;
}
+ &--undetermined {
+ color: $ui-fleet-black-50;
+ font-style: italic;
+ font-weight: 400;
+ padding: 0;
+ border-radius: 0;
+ }
+ &--minimal {
+ background-color: $ui-vibrant-blue-10;
+ }
+ &--considerable {
+ background-color: $ui-vibrant-blue-25;
+ }
+ &--excessive {
+ background-color: $ui-vibrant-blue-50;
+ }
+ }
+
+ .tooltip {
+ display: flex;
+ justify-content: center;
}
&__status {
diff --git a/frontend/components/queries/PackQueriesListWrapper/PackQueriesTable/PackQueriesTableConfig.tsx b/frontend/components/queries/PackQueriesListWrapper/PackQueriesTable/PackQueriesTableConfig.tsx
index 170b3b5d7..57d2897ff 100644
--- a/frontend/components/queries/PackQueriesListWrapper/PackQueriesTable/PackQueriesTableConfig.tsx
+++ b/frontend/components/queries/PackQueriesListWrapper/PackQueriesTable/PackQueriesTableConfig.tsx
@@ -4,10 +4,12 @@
import React from "react";
import { find } from "lodash";
+import { performanceIndicator } from "fleet/helpers";
import Checkbox from "components/forms/fields/Checkbox";
-import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell";
-import TextCell from "components/TableContainer/DataTable/TextCell";
import DropdownCell from "components/TableContainer/DataTable/DropdownCell";
+import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell";
+import PillCell from "components/TableContainer/DataTable/PillCell";
+import TextCell from "components/TableContainer/DataTable/TextCell";
import { IScheduledQuery } from "interfaces/scheduled_query";
import { IDropdownOption } from "interfaces/dropdownOption";
@@ -108,6 +110,13 @@ const generateTableHeaders = (
accessor: "loggingTypeString",
Cell: (cellProps) => ,
},
+ {
+ title: "Performance impact",
+ Header: "Performance impact",
+ disableSortBy: true,
+ accessor: "performance",
+ Cell: (cellProps) => ,
+ },
{
title: "Actions",
Header: "",
@@ -200,6 +209,11 @@ const enhancePackQueriesData = (
packQueries: IScheduledQuery[]
): IPackQueriesTableData[] => {
return packQueries.map((query) => {
+ const scheduledQueryPerformance = {
+ user_time_p50: query.stats?.user_time_p50,
+ system_time_p50: query.stats?.system_time_p50,
+ total_executions: query.stats?.total_executions,
+ };
return {
id: query.id,
name: query.name,
@@ -222,6 +236,11 @@ const enhancePackQueriesData = (
updated_at: query.updated_at,
query_name: query.query_name,
actions: generateActionDropdownOptions(),
+ performance: [
+ performanceIndicator(scheduledQueryPerformance),
+ query.query_id,
+ ],
+ stats: query.stats,
};
});
};
diff --git a/frontend/fleet/helpers.ts b/frontend/fleet/helpers.ts
index 2647a5f65..3e14b667d 100644
--- a/frontend/fleet/helpers.ts
+++ b/frontend/fleet/helpers.ts
@@ -6,10 +6,7 @@ import yaml from "js-yaml";
import { ILabel } from "interfaces/label";
import { ITeam } from "interfaces/team";
import { IUser } from "interfaces/user";
-import {
- IPackQueryFormData,
- IScheduledQuery,
-} from "interfaces/scheduled_query";
+import { IPackQueryFormData } from "interfaces/scheduled_query";
import stringUtils from "utilities/strings";
import sortUtils from "utilities/sort";
@@ -17,6 +14,7 @@ import {
DEFAULT_GRAVATAR_LINK,
PLATFORM_LABEL_DISPLAY_TYPES,
} from "utilities/constants";
+import { IScheduledQueryStats } from "interfaces/scheduled_query_stats";
const ORG_INFO_ATTRS = ["org_name", "org_logo_url"];
const ADMIN_ATTRS = ["email", "name", "password", "password_confirmation"];
@@ -592,6 +590,35 @@ export const licenseExpirationWarning = (expiration: string): boolean => {
return moment(moment()).isAfter(expiration);
};
+// IQueryStats became any when adding in IGlobalScheduledQuery and ITeamScheduledQuery
+export const performanceIndicator = (
+ scheduledQueryStats: IScheduledQueryStats
+): string => {
+ if (
+ !scheduledQueryStats.total_executions ||
+ scheduledQueryStats.total_executions === 0 ||
+ scheduledQueryStats.total_executions === null
+ ) {
+ return "Undetermined";
+ }
+
+ if (
+ typeof scheduledQueryStats.user_time_p50 === "number" &&
+ typeof scheduledQueryStats.system_time_p50 === "number"
+ ) {
+ const indicator =
+ scheduledQueryStats.user_time_p50 + scheduledQueryStats.system_time_p50;
+
+ if (indicator < 2000) {
+ return "Minimal";
+ }
+ if (indicator < 4000) {
+ return "Considerable";
+ }
+ }
+ return "Excessive";
+};
+
export const secondsToHms = (d: number): string => {
const h = Math.floor(d / 3600);
const m = Math.floor((d % 3600) / 60);
diff --git a/frontend/interfaces/global_scheduled_query.ts b/frontend/interfaces/global_scheduled_query.ts
index 98b3fb61b..9d9ac2772 100644
--- a/frontend/interfaces/global_scheduled_query.ts
+++ b/frontend/interfaces/global_scheduled_query.ts
@@ -1,5 +1,9 @@
import PropTypes from "prop-types";
+import scheduledQueryStatsInterface, {
+ IScheduledQueryStats,
+} from "./scheduled_query_stats";
+
export default PropTypes.shape({
created_at: PropTypes.string,
updated_at: PropTypes.string,
@@ -16,6 +20,7 @@ export default PropTypes.shape({
version: PropTypes.string,
shard: PropTypes.number,
denylist: PropTypes.bool,
+ stats: scheduledQueryStatsInterface,
});
export interface IGlobalScheduledQuery {
@@ -34,4 +39,5 @@ export interface IGlobalScheduledQuery {
version?: string;
shard?: number;
denylist?: boolean;
+ stats?: IScheduledQueryStats;
}
diff --git a/frontend/interfaces/query.ts b/frontend/interfaces/query.ts
index 70002d135..dde229163 100644
--- a/frontend/interfaces/query.ts
+++ b/frontend/interfaces/query.ts
@@ -1,6 +1,9 @@
import PropTypes from "prop-types";
import { IFormField } from "./form_field";
import packInterface, { IPack } from "./pack";
+import scheduledQueryStatsInterface, {
+ IScheduledQueryStats,
+} from "./scheduled_query_stats";
export default PropTypes.shape({
created_at: PropTypes.string,
@@ -14,6 +17,7 @@ export default PropTypes.shape({
author_name: PropTypes.string,
observer_can_run: PropTypes.bool,
packs: PropTypes.arrayOf(packInterface),
+ stats: scheduledQueryStatsInterface,
});
export interface IQueryFormData {
description?: string | number | boolean | any[] | undefined;
@@ -34,6 +38,7 @@ export interface IQuery {
author_name: string;
observer_can_run: boolean;
packs: IPack[];
+ stats?: IScheduledQueryStats;
}
export interface IQueryFormFields {
diff --git a/frontend/interfaces/query_stats.ts b/frontend/interfaces/query_stats.ts
index dad3fb7a9..b6906c10d 100644
--- a/frontend/interfaces/query_stats.ts
+++ b/frontend/interfaces/query_stats.ts
@@ -1,5 +1,9 @@
import PropTypes, { number } from "prop-types";
+import scheduledQueryStatsInterface, {
+ IScheduledQueryStats,
+} from "./scheduled_query_stats";
+
export default PropTypes.shape({
scheduled_query_name: PropTypes.string,
scheduled_query_id: PropTypes.number,
@@ -16,6 +20,7 @@ export default PropTypes.shape({
system_time: PropTypes.number,
user_time: PropTypes.number,
wall_time: PropTypes.number,
+ stats: scheduledQueryStatsInterface,
});
export interface IQueryStats {
@@ -34,4 +39,5 @@ export interface IQueryStats {
system_time: number;
user_time: number;
wall_time?: number;
+ stats?: IScheduledQueryStats;
}
diff --git a/frontend/interfaces/scheduled_query.ts b/frontend/interfaces/scheduled_query.ts
index 48e22a5d2..8f4d8460b 100644
--- a/frontend/interfaces/scheduled_query.ts
+++ b/frontend/interfaces/scheduled_query.ts
@@ -1,4 +1,7 @@
import PropTypes from "prop-types";
+import scheduledQueryStatsInterface, {
+ IScheduledQueryStats,
+} from "./scheduled_query_stats";
export default PropTypes.shape({
created_at: PropTypes.string,
@@ -16,6 +19,7 @@ export default PropTypes.shape({
version: PropTypes.string,
shard: PropTypes.number,
denylist: PropTypes.bool,
+ stats: scheduledQueryStatsInterface,
});
export interface IPackQueryFormData {
@@ -48,4 +52,5 @@ export interface IScheduledQuery {
shard: number | null;
denylist?: boolean;
logging_type?: string;
+ stats: IScheduledQueryStats;
}
diff --git a/frontend/interfaces/scheduled_query_stats.ts b/frontend/interfaces/scheduled_query_stats.ts
new file mode 100644
index 000000000..20e7699e2
--- /dev/null
+++ b/frontend/interfaces/scheduled_query_stats.ts
@@ -0,0 +1,17 @@
+import PropTypes, { number } from "prop-types";
+
+export default PropTypes.shape({
+ user_time_p50: PropTypes.number,
+ user_time_p95: PropTypes.number,
+ system_time_p50: PropTypes.number,
+ system_time_p95: PropTypes.number,
+ total_executions: PropTypes.number,
+});
+
+export interface IScheduledQueryStats {
+ user_time_p50?: number;
+ user_time_p95?: number;
+ system_time_p50?: number;
+ system_time_p95?: number;
+ total_executions?: number;
+}
diff --git a/frontend/interfaces/team_scheduled_query.ts b/frontend/interfaces/team_scheduled_query.ts
index 550009d98..8c8f1b4ec 100644
--- a/frontend/interfaces/team_scheduled_query.ts
+++ b/frontend/interfaces/team_scheduled_query.ts
@@ -1,5 +1,9 @@
import PropTypes from "prop-types";
+import scheduledQueryStatsInterface, {
+ IScheduledQueryStats,
+} from "./scheduled_query_stats";
+
export default PropTypes.shape({
created_at: PropTypes.string,
updated_at: PropTypes.string,
@@ -15,6 +19,8 @@ export default PropTypes.shape({
shard: PropTypes.number,
platform: PropTypes.string,
version: PropTypes.string,
+ denylist: PropTypes.bool,
+ stats: scheduledQueryStatsInterface,
});
export interface ITeamScheduledQuery {
@@ -32,4 +38,6 @@ export interface ITeamScheduledQuery {
platform?: string;
version?: string;
shard?: number;
+ denylist?: boolean;
+ stats?: IScheduledQueryStats;
}
diff --git a/frontend/pages/hosts/HostDetailsPage/PackTable/PackTableConfig.tsx b/frontend/pages/hosts/HostDetailsPage/PackTable/PackTableConfig.tsx
index 4b7e5108c..c80c8a2ca 100644
--- a/frontend/pages/hosts/HostDetailsPage/PackTable/PackTableConfig.tsx
+++ b/frontend/pages/hosts/HostDetailsPage/PackTable/PackTableConfig.tsx
@@ -3,7 +3,11 @@ import React from "react";
import TextCell from "components/TableContainer/DataTable/TextCell";
import PillCell from "components/TableContainer/DataTable/PillCell";
import { IQueryStats } from "interfaces/query_stats";
-import { humanQueryLastRun, secondsToHms } from "fleet/helpers";
+import {
+ humanQueryLastRun,
+ performanceIndicator,
+ secondsToHms,
+} from "fleet/helpers";
import IconToolTip from "components/IconToolTip";
interface IHeaderProps {
@@ -37,27 +41,6 @@ interface IPackTable extends IQueryStats {
performance: (string | number)[];
}
-const performanceIndicator = (scheduledQuery: IQueryStats): string => {
- if (scheduledQuery.executions === 0) {
- return "Undetermined";
- }
- if (scheduledQuery.denylisted === true) {
- return "Denylisted";
- }
-
- const indicator =
- (scheduledQuery.user_time + scheduledQuery.system_time) /
- scheduledQuery.executions;
-
- if (indicator < 2000) {
- return "Minimal";
- }
- if (indicator >= 2000 && indicator <= 4000) {
- return "Considerable";
- }
- return "Excessive";
-};
-
// NOTE: cellProps come from react-table
// more info here https://react-table.tanstack.com/docs/api/useTable#cell-properties
const generatePackTableHeaders = (): IDataColumn[] => {
@@ -110,6 +93,11 @@ const generatePackTableHeaders = (): IDataColumn[] => {
const enhancePackData = (query_stats: IQueryStats[]): IPackTable[] => {
return Object.values(query_stats).map((query) => {
+ const scheduledQueryPerformance = {
+ user_time_p50: query.user_time,
+ system_time_p50: query.system_time,
+ total_executions: query.executions,
+ };
return {
scheduled_query_name: query.scheduled_query_name,
scheduled_query_id: query.scheduled_query_id,
@@ -121,7 +109,10 @@ const enhancePackData = (query_stats: IQueryStats[]): IPackTable[] => {
last_executed: query.last_executed,
frequency: secondsToHms(query.interval),
last_run: humanQueryLastRun(query.last_executed),
- performance: [performanceIndicator(query), query.scheduled_query_id],
+ performance: [
+ performanceIndicator(scheduledQueryPerformance),
+ query.scheduled_query_id,
+ ],
average_memory: query.average_memory,
denylisted: query.denylisted,
executions: query.executions,
diff --git a/frontend/pages/hosts/HostDetailsPage/_styles.scss b/frontend/pages/hosts/HostDetailsPage/_styles.scss
index efa18b39b..d257d3381 100644
--- a/frontend/pages/hosts/HostDetailsPage/_styles.scss
+++ b/frontend/pages/hosts/HostDetailsPage/_styles.scss
@@ -533,36 +533,6 @@
display: none;
}
- .tooltip {
- width: 192px;
- }
-
- .data-table__pill--undetermined {
- color: $ui-fleet-black-50;
- font-style: italic;
- font-weight: 400;
- padding: 0;
- border-radius: 0;
- }
-
- .data-table__pill--denylisted {
- font-weight: 400;
- padding: 0;
- border-radius: 0;
- }
-
- .data-table__pill--minimal {
- background-color: $ui-vibrant-blue-10;
- }
-
- .data-table__pill--considerable {
- background-color: $ui-vibrant-blue-25;
- }
-
- .data-table__pill--excessive {
- background-color: $ui-vibrant-blue-50;
- }
-
.data-table__table {
table-layout: fixed;
diff --git a/frontend/pages/queries/ManageQueriesPage/components/QueriesListWrapper/QueriesListWrapper.tsx b/frontend/pages/queries/ManageQueriesPage/components/QueriesListWrapper/QueriesListWrapper.tsx
index d8e2fc95a..f445667fe 100644
--- a/frontend/pages/queries/ManageQueriesPage/components/QueriesListWrapper/QueriesListWrapper.tsx
+++ b/frontend/pages/queries/ManageQueriesPage/components/QueriesListWrapper/QueriesListWrapper.tsx
@@ -5,8 +5,7 @@ import { IQuery } from "interfaces/query";
import { IUser } from "interfaces/user";
import Button from "components/buttons/Button";
import TableContainer from "components/TableContainer";
-
-import generateTableHeaders from "./QueriesTableConfig";
+import { generateTableHeaders, generateDataSet } from "./QueriesTableConfig";
const baseClass = "queries-list-wrapper";
const noQueriesClass = "no-queries";
@@ -109,13 +108,14 @@ const QueriesListWrapper = (
}, [searchString, onCreateQueryClick]);
const tableHeaders = generateTableHeaders(currentUser);
+ const dataSet = generateDataSet(filteredQueries);
return !isLoading ? (
{
),
sortType: "caseInsensitive",
},
+ {
+ title: "Performance impact",
+ Header: "Performance impact",
+ disableSortBy: true,
+ accessor: "performance",
+ Cell: (cellProps) => ,
+ },
{
title: "Author",
Header: (cellProps) => (
@@ -216,4 +232,25 @@ const generateTableHeaders = (currentUser: IUser): IDataColumn[] => {
return tableHeaders;
};
-export default generateTableHeaders;
+const enhanceQueryData = (queries: IQuery[]): IQueryTableData[] => {
+ return queries.map((query: IQuery) => {
+ const scheduledQueryPerformance = {
+ user_time_p50: query.stats?.user_time_p50,
+ system_time_p50: query.stats?.system_time_p50,
+ total_executions: query.stats?.total_executions,
+ };
+ return {
+ name: query.name,
+ id: query.id,
+ author_name: query.author_name,
+ updated_at: query.updated_at,
+ performance: [performanceIndicator(scheduledQueryPerformance), query.id],
+ };
+ });
+};
+
+const generateDataSet = (queries: IQuery[]): IQueryTableData[] => {
+ return [...enhanceQueryData(queries)];
+};
+
+export { generateTableHeaders, generateDataSet };
diff --git a/frontend/pages/schedule/ManageSchedulePage/_styles.scss b/frontend/pages/schedule/ManageSchedulePage/_styles.scss
index c6c96dfad..1caad6dba 100644
--- a/frontend/pages/schedule/ManageSchedulePage/_styles.scss
+++ b/frontend/pages/schedule/ManageSchedulePage/_styles.scss
@@ -235,11 +235,16 @@
}
.query_name__header {
- width: calc(63%);
+ width: calc(50%);
}
.interval__header {
- width: calc(37%);
+ width: calc(20%);
+ }
+
+ .performance_header {
+ width: calc(30%);
+ max-width: none;
}
}
}
diff --git a/frontend/pages/schedule/ManageSchedulePage/components/ScheduleListWrapper/ScheduleTableConfig.tsx b/frontend/pages/schedule/ManageSchedulePage/components/ScheduleListWrapper/ScheduleTableConfig.tsx
index aa4e5f56b..67502b473 100644
--- a/frontend/pages/schedule/ManageSchedulePage/components/ScheduleListWrapper/ScheduleTableConfig.tsx
+++ b/frontend/pages/schedule/ManageSchedulePage/components/ScheduleListWrapper/ScheduleTableConfig.tsx
@@ -2,12 +2,13 @@
// disable this rule as it was throwing an error in Header and Cell component
// definitions for the selection row for some reason when we dont really need it.
import React from "react";
-import { secondsToDhms } from "fleet/helpers";
+import { performanceIndicator, secondsToDhms } from "fleet/helpers";
// @ts-ignore
import Checkbox from "components/forms/fields/Checkbox";
import TextCell from "components/TableContainer/DataTable/TextCell";
import DropdownCell from "components/TableContainer/DataTable/DropdownCell";
+import PillCell from "components/TableContainer/DataTable/PillCell";
import { IDropdownOption } from "interfaces/dropdownOption";
import { IGlobalScheduledQuery } from "interfaces/global_scheduled_query";
import { ITeamScheduledQuery } from "interfaces/team_scheduled_query";
@@ -97,6 +98,13 @@ const generateTableHeaders = (
),
},
+ {
+ title: "Performance impact",
+ Header: "Performance impact",
+ disableSortBy: true,
+ accessor: "performance",
+ Cell: (cellProps) => ,
+ },
{
title: "Actions",
Header: "",
@@ -135,6 +143,13 @@ const generateInheritedQueriesTableHeaders = (): IDataColumn[] => {
),
},
+ {
+ title: "Performance impact",
+ Header: "Performance impact",
+ disableSortBy: true,
+ accessor: "performance",
+ Cell: (cellProps) => ,
+ },
];
};
@@ -160,6 +175,11 @@ const enhanceAllScheduledQueryData = (
): IAllScheduledQueryTableData[] => {
return all_scheduled_queries.map(
(all_scheduled_query: IGlobalScheduledQuery | ITeamScheduledQuery) => {
+ const scheduledQueryPerformance = {
+ user_time_p50: all_scheduled_query.stats?.user_time_p50,
+ system_time_p50: all_scheduled_query.stats?.system_time_p50,
+ total_executions: all_scheduled_query.stats?.total_executions,
+ };
return {
name: all_scheduled_query.name,
query_name: all_scheduled_query.query_name,
@@ -173,6 +193,10 @@ const enhanceAllScheduledQueryData = (
version: all_scheduled_query.version,
shard: all_scheduled_query.shard,
type: teamId ? "team_scheduled_query" : "global_scheduled_query",
+ performance: [
+ performanceIndicator(scheduledQueryPerformance),
+ all_scheduled_query.id,
+ ],
};
}
);
diff --git a/frontend/pages/schedule/ManageSchedulePage/components/ScheduleListWrapper/_styles.scss b/frontend/pages/schedule/ManageSchedulePage/components/ScheduleListWrapper/_styles.scss
index 92b398034..263662135 100644
--- a/frontend/pages/schedule/ManageSchedulePage/components/ScheduleListWrapper/_styles.scss
+++ b/frontend/pages/schedule/ManageSchedulePage/components/ScheduleListWrapper/_styles.scss
@@ -35,6 +35,9 @@
width: 150px;
border-left: 0;
}
+ .performance__header {
+ width: 20% !important;
+ }
}
}
diff --git a/server/datastore/mysql/aggregated_stats.go b/server/datastore/mysql/aggregated_stats.go
index cf48c7902..11866a244 100644
--- a/server/datastore/mysql/aggregated_stats.go
+++ b/server/datastore/mysql/aggregated_stats.go
@@ -15,7 +15,7 @@ import (
const scheduledQueryPercentileQuery = `
SELECT
- (t1.%s / t1.executions)
+ coalesce((t1.%s / t1.executions), 0)
FROM (
SELECT @rownum := @rownum + 1 AS row_number, mm.* FROM (
SELECT d.scheduled_query_id, d.%s, d.executions
@@ -34,7 +34,7 @@ WHERE t1.row_number = floor(total_rows * %s) + 1;`
const queryPercentileQuery = `
SELECT
- (t1.%s / t1.executions)
+ coalesce((t1.%s / t1.executions), 0)
FROM (
SELECT @rownum := @rownum + 1 AS row_number, mm.* FROM (
SELECT d.scheduled_query_id, d.%s, d.executions
diff --git a/server/datastore/mysql/aggregated_stats_test.go b/server/datastore/mysql/aggregated_stats_test.go
index f45910496..85e8dfc6c 100644
--- a/server/datastore/mysql/aggregated_stats_test.go
+++ b/server/datastore/mysql/aggregated_stats_test.go
@@ -117,7 +117,8 @@ select
JSON_EXTRACT(json_value, "$.user_time_p50") as user_time_p50,
JSON_EXTRACT(json_value, "$.user_time_p95") as user_time_p95,
JSON_EXTRACT(json_value, "$.system_time_p50") as system_time_p50,
- JSON_EXTRACT(json_value, "$.system_time_p95") as system_time_p95
+ JSON_EXTRACT(json_value, "$.system_time_p95") as system_time_p95,
+ JSON_EXTRACT(json_value, "$.total_executions") as total_executions
from aggregated_stats where type=?`, tt.aggregate))
require.True(t, len(stats) > 0)
@@ -126,6 +127,8 @@ from aggregated_stats where type=?`, tt.aggregate))
checkAgainstSlowStats(t, ds, stat.ID, 95, tt.table, "user_time", stat.UserTimeP95)
checkAgainstSlowStats(t, ds, stat.ID, 50, tt.table, "system_time", stat.SystemTimeP50)
checkAgainstSlowStats(t, ds, stat.ID, 95, tt.table, "system_time", stat.SystemTimeP95)
+ require.NotNil(t, stat.TotalExecutions)
+ assert.True(t, *stat.TotalExecutions >= 0)
}
})
}
diff --git a/server/datastore/mysql/queries.go b/server/datastore/mysql/queries.go
index 2787c8b1f..d66100680 100644
--- a/server/datastore/mysql/queries.go
+++ b/server/datastore/mysql/queries.go
@@ -178,7 +178,8 @@ func (d *Datastore) ListQueries(ctx context.Context, opt fleet.ListQueryOptions)
JSON_EXTRACT(json_value, "$.user_time_p50") as user_time_p50,
JSON_EXTRACT(json_value, "$.user_time_p95") as user_time_p95,
JSON_EXTRACT(json_value, "$.system_time_p50") as system_time_p50,
- JSON_EXTRACT(json_value, "$.system_time_p95") as system_time_p95
+ JSON_EXTRACT(json_value, "$.system_time_p95") as system_time_p95,
+ JSON_EXTRACT(json_value, "$.total_executions") as total_executions
FROM queries q
LEFT JOIN users u ON (q.author_id = u.id)
LEFT JOIN aggregated_stats ag ON (ag.id=q.id AND ag.type="query")
diff --git a/server/datastore/mysql/scheduled_queries.go b/server/datastore/mysql/scheduled_queries.go
index 77e61ad3a..14966b753 100644
--- a/server/datastore/mysql/scheduled_queries.go
+++ b/server/datastore/mysql/scheduled_queries.go
@@ -30,7 +30,8 @@ func (d *Datastore) ListScheduledQueriesInPack(ctx context.Context, id uint, opt
JSON_EXTRACT(json_value, "$.user_time_p50") as user_time_p50,
JSON_EXTRACT(json_value, "$.user_time_p95") as user_time_p95,
JSON_EXTRACT(json_value, "$.system_time_p50") as system_time_p50,
- JSON_EXTRACT(json_value, "$.system_time_p95") as system_time_p95
+ JSON_EXTRACT(json_value, "$.system_time_p95") as system_time_p95,
+ JSON_EXTRACT(json_value, "$.total_executions") as total_executions
FROM scheduled_queries sq
JOIN queries q ON (sq.query_name = q.name)
LEFT JOIN aggregated_stats ag ON (ag.id=sq.id AND ag.type="scheduled_query")
diff --git a/server/fleet/scheduled_queries.go b/server/fleet/scheduled_queries.go
index 00dc09ef7..64d31766c 100644
--- a/server/fleet/scheduled_queries.go
+++ b/server/fleet/scheduled_queries.go
@@ -30,7 +30,7 @@ type AggregatedStats struct {
SystemTimeP50 *float64 `json:"system_time_p50" db:"system_time_p50"`
SystemTimeP95 *float64 `json:"system_time_p95" db:"system_time_p95"`
UserTimeP50 *float64 `json:"user_time_p50" db:"user_time_p50"`
- UserTimeP95 *float64 `json:"user_time_p_95" db:"user_time_p95"`
+ UserTimeP95 *float64 `json:"user_time_p95" db:"user_time_p95"`
TotalExecutions *float64 `json:"total_executions" db:"total_executions"`
}