mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
UI feature: Frontend of performance impact bubbles (#2589)
Includes backend fixes and test Co-authored-by: Tomas Touceda <chiiph@gmail.com>
This commit is contained in:
parent
0fb6416d45
commit
36babcc510
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 => {
|
||||
>
|
||||
<span
|
||||
className={`tooltip ${generateClassTag(pillText)}__tooltip-text`}
|
||||
style={{ width: "196px" }}
|
||||
style={{ textAlign: "center" }}
|
||||
>
|
||||
{tooltipText()}
|
||||
</span>
|
||||
|
@ -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 {
|
||||
|
@ -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) => <TextCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
title: "Performance impact",
|
||||
Header: "Performance impact",
|
||||
disableSortBy: true,
|
||||
accessor: "performance",
|
||||
Cell: (cellProps) => <PillCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
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,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
17
frontend/interfaces/scheduled_query_stats.ts
Normal file
17
frontend/interfaces/scheduled_query_stats.ts
Normal file
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 ? (
|
||||
<div className={`${baseClass}`}>
|
||||
<TableContainer
|
||||
resultsTitle={"queries"}
|
||||
columns={tableHeaders}
|
||||
data={filteredQueries}
|
||||
data={dataSet}
|
||||
isLoading={isLoading}
|
||||
defaultSortHeader={"query"}
|
||||
defaultSortDirection={"desc"}
|
||||
|
@ -11,6 +11,8 @@ import Checkbox from "components/forms/fields/Checkbox";
|
||||
import LinkCell from "components/TableContainer/DataTable/LinkCell/LinkCell";
|
||||
import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell";
|
||||
import TextCell from "components/TableContainer/DataTable/TextCell";
|
||||
import PillCell from "components/TableContainer/DataTable/PillCell";
|
||||
import { performanceIndicator } from "fleet/helpers";
|
||||
|
||||
import PATHS from "router/paths";
|
||||
|
||||
@ -57,6 +59,13 @@ interface IDataColumn {
|
||||
disableSortBy?: boolean;
|
||||
sortType?: string;
|
||||
}
|
||||
interface IQueryTableData {
|
||||
name: string;
|
||||
id: number;
|
||||
author_name: string;
|
||||
updated_at: string;
|
||||
performance: (string | number)[];
|
||||
}
|
||||
|
||||
// NOTE: cellProps come from react-table
|
||||
// more info here https://react-table.tanstack.com/docs/api/useTable#cell-properties
|
||||
@ -82,6 +91,13 @@ const generateTableHeaders = (currentUser: IUser): IDataColumn[] => {
|
||||
),
|
||||
sortType: "caseInsensitive",
|
||||
},
|
||||
{
|
||||
title: "Performance impact",
|
||||
Header: "Performance impact",
|
||||
disableSortBy: true,
|
||||
accessor: "performance",
|
||||
Cell: (cellProps) => <PillCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
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 };
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 = (
|
||||
<TextCell value={secondsToDhms(cellProps.cell.value)} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Performance impact",
|
||||
Header: "Performance impact",
|
||||
disableSortBy: true,
|
||||
accessor: "performance",
|
||||
Cell: (cellProps) => <PillCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
title: "Actions",
|
||||
Header: "",
|
||||
@ -135,6 +143,13 @@ const generateInheritedQueriesTableHeaders = (): IDataColumn[] => {
|
||||
<TextCell value={secondsToDhms(cellProps.cell.value)} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Performance impact",
|
||||
Header: "Performance impact",
|
||||
disableSortBy: true,
|
||||
accessor: "performance",
|
||||
Cell: (cellProps) => <PillCell value={cellProps.cell.value} />,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
@ -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,
|
||||
],
|
||||
};
|
||||
}
|
||||
);
|
||||
|
@ -35,6 +35,9 @@
|
||||
width: 150px;
|
||||
border-left: 0;
|
||||
}
|
||||
.performance__header {
|
||||
width: 20% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
|
@ -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"`
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user