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"` }