Skip to content

Commit

Permalink
ui: add insight count to sql activity execution fingerprint overviews
Browse files Browse the repository at this point in the history
This commit adds a column for the distinct insight types for each
fingerprint listed on the Statement and Transaction Fingerprint
Overview pages. The commit also reverts some of the additional
middleware added in service of insights on the fingerprint details
pages, in favor of reusing existing insights middleware.

Fixes cockroachdb#83780.

Release note (ui change): Added Insights column to the SQL Activity
Fingerprint Overview pages of the DB Console.
  • Loading branch information
ericharmeling committed Feb 16, 2023
1 parent 377fe75 commit dae5bd2
Show file tree
Hide file tree
Showing 33 changed files with 416 additions and 273 deletions.
2 changes: 1 addition & 1 deletion pkg/ui/workspaces/cluster-ui/src/api/stmtInsightsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ txn_id,
txn_fingerprint_id,
implicit_txn,
stmt_id,
stmt_fingerprint_id,
encode(stmt_fingerprint_id, 'hex') as stmt_fingerprint_id,
query,
start_time,
end_time,
Expand Down
5 changes: 5 additions & 0 deletions pkg/ui/workspaces/cluster-ui/src/insights/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ export type ContentionEvent = {
stmtInsightEvent: StmtInsightEvent;
};

export type ExecutionInsightCount = {
fingerprintID: string;
insightCount: number;
};

export const highContentionInsight = (
execType: InsightExecEnum,
latencyThresholdMs?: number,
Expand Down
74 changes: 74 additions & 0 deletions pkg/ui/workspaces/cluster-ui/src/insights/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import { unset } from "src/util";
import {
ExecutionDetails,
ExecutionInsightCount,
getInsightFromCause,
Insight,
InsightExecEnum,
Expand All @@ -23,6 +24,7 @@ import {
TxnInsightEvent,
WorkloadInsightEventFilters,
} from "./types";
import { indexOf } from "lodash";

export const filterTransactionInsights = (
transactions: TxnInsightEvent[] | null,
Expand Down Expand Up @@ -420,3 +422,75 @@ export function getTxnInsightRecommendations(
);
return recs;
}

// The functions below de-duplicate insights of the same type for a given fingerprint.
// The front-end requires de-duplicated insights for the fingerprints pages because the backend has a problem+cause
// definition for insight types, and the SQL API query selects distinct on fingerprint, problem, and cause.

export function getStmtFingerprintInsightCounts(
insightEvents: StmtInsightEvent[],
): ExecutionInsightCount[] {
if (!insightEvents || insightEvents.length === 0) {
return null;
}
const result: ExecutionInsightCount[] = [];
const stmtInsightMap = new Map<string, Set<InsightNameEnum>>();
insightEvents.forEach(event => {
if (!stmtInsightMap.has(event.statementFingerprintID)) {
const stmtInsightTypes = new Set<InsightNameEnum>();
event.insights.forEach(insight => stmtInsightTypes.add(insight.name));
stmtInsightMap.set(event.statementFingerprintID, stmtInsightTypes);
result.push({
fingerprintID: event.statementFingerprintID,
insightCount: event.insights.length,
});
} else {
const mapValues = stmtInsightMap.get(event.statementFingerprintID);
event.insights.forEach(insight => {
if (!mapValues.has(insight.name)) {
mapValues.add(insight.name);
const idx = result.findIndex(
item => item.fingerprintID === event.statementFingerprintID,
);
result[idx].insightCount += 1;
}
});
}
});

return result;
}

export function getTxnFingerprintInsightCounts(
insightEvents: TxnInsightEvent[],
): ExecutionInsightCount[] {
if (!insightEvents || insightEvents.length === 0) {
return null;
}
const result: ExecutionInsightCount[] = [];
const txnInsightMap = new Map<string, Set<InsightNameEnum>>();
insightEvents.forEach(event => {
if (!txnInsightMap.has(event.transactionFingerprintID)) {
const txnInsightTypes = new Set<InsightNameEnum>();
event.insights.forEach(insight => txnInsightTypes.add(insight.name));
txnInsightMap.set(event.transactionFingerprintID, txnInsightTypes);
result.push({
fingerprintID: event.transactionFingerprintID,
insightCount: event.insights.length,
});
} else {
const mapValues = txnInsightMap.get(event.transactionFingerprintID);
event.insights.forEach(insight => {
if (!mapValues.has(insight.name)) {
mapValues.add(insight.name);
const idx = result.findIndex(
item => item.fingerprintID === event.transactionFingerprintID,
);
result[idx].insightCount += 1;
}
});
}
});

return result;
}
Original file line number Diff line number Diff line change
Expand Up @@ -858,7 +858,7 @@ export const getStatementDetailsPropsFixture = (
refreshNodes: noop,
refreshNodesLiveness: noop,
refreshUserSQLRoles: noop,
refreshStatementFingerprintInsights: noop,
refreshStatementInsights: noop,
diagnosticsReports: [],
dismissStatementDiagnosticsAlertMessage: noop,
onTimeScaleChange: noop,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

import React, { ReactNode, useContext, useMemo } from "react";
import React, { ReactNode, useContext } from "react";
import { Col, Row, Tabs } from "antd";
import "antd/lib/col/style";
import "antd/lib/row/style";
Expand Down Expand Up @@ -55,6 +55,7 @@ import {
TimeScale,
timeScale1hMinOptions,
TimeScaleDropdown,
timeScaleRangeToObj,
timeScaleToString,
toRoundedDateRange,
} from "../timeScaleDropdown";
Expand All @@ -81,7 +82,6 @@ import {
} from "../api";
import {
getStmtInsightRecommendations,
InsightNameEnum,
InsightType,
StmtInsightEvent,
} from "../insights";
Expand Down Expand Up @@ -124,7 +124,7 @@ export interface StatementDetailsDispatchProps {
refreshUserSQLRoles: () => void;
refreshNodes: () => void;
refreshNodesLiveness: () => void;
refreshStatementFingerprintInsights: (req: StmtInsightsReq) => void;
refreshStatementInsights: (req: StmtInsightsReq) => void;
createStatementDiagnosticsReport: (
insertStmtDiagnosticsRequest: InsertStmtDiagnosticRequest,
) => void;
Expand Down Expand Up @@ -155,7 +155,7 @@ export interface StatementDetailsStateProps {
isTenant?: UIConfigState["isTenant"];
hasViewActivityRedactedRole?: UIConfigState["hasViewActivityRedactedRole"];
hasAdminRole?: UIConfigState["hasAdminRole"];
statementFingerprintInsights?: StmtInsightEvent[];
statementInsights?: StmtInsightEvent[];
}

export type StatementDetailsOwnProps = StatementDetailsDispatchProps &
Expand Down Expand Up @@ -267,23 +267,15 @@ export class StatementDetails extends React.Component<
}

refreshStatementDetails = () => {
if (!this.props.statementInsights) {
const insightsReq = timeScaleRangeToObj(this.props.timeScale);
this.props.refreshStatementInsights(insightsReq);
}
const req = getStatementDetailsRequestFromProps(this.props);
this.props.refreshStatementDetails(req);
this.refreshStatementInsights();
this.resetPolling(this.props.timeScale.key);
};

refreshStatementInsights = () => {
const [startTime, endTime] = toRoundedDateRange(this.props.timeScale);
const id = BigInt(this.props.statementFingerprintID).toString(16);
const req: StmtInsightsReq = {
start: startTime,
end: endTime,
stmtFingerprintId: id,
};
this.props.refreshStatementFingerprintInsights(req);
};

handleResize = (): void => {
// Use the same size as the summary card and remove a space for margin (22).
const cardWidth = document.getElementById("first-card")
Expand Down Expand Up @@ -552,7 +544,7 @@ export class StatementDetails extends React.Component<
return this.renderNoDataWithTimeScaleAndSqlBoxTabContent(hasTimeout);
}
const { cardWidth } = this.state;
const { nodeRegions, isTenant, statementFingerprintInsights } = this.props;
const { nodeRegions, isTenant, statementInsights } = this.props;
const { stats } = this.props.statementDetails.statement;
const {
app_names,
Expand Down Expand Up @@ -668,9 +660,9 @@ export class StatementDetails extends React.Component<
false,
);
const tableData: InsightRecommendation[] = [];
if (statementFingerprintInsights) {
if (statementInsights) {
const tableDataTypes = new Set<InsightType>();
statementFingerprintInsights.forEach(insight => {
statementInsights.forEach(insight => {
const rec = getStmtInsightRecommendations(insight);
rec.forEach(entry => {
if (!tableDataTypes.has(entry.type)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ import { actions as analyticsActions } from "src/store/analytics";
import { actions as localStorageActions } from "src/store/localStorage";
import { actions as nodesActions } from "../store/nodes";
import { actions as nodeLivenessActions } from "../store/liveness";
import { selectTimeScale } from "../store/utils/selectors";
import {
actions as statementFingerprintInsightActions,
selectStatementFingerprintInsights,
} from "src/store/insights/statementFingerprintInsights";
actions as statementInsightActions,
selectInsightsByFingerprint,
} from "../store/insights/statementInsights";
import { selectTimeScale } from "../store/utils/selectors";
import {
StmtInsightsReq,
InsertStmtDiagnosticRequest,
Expand Down Expand Up @@ -76,10 +76,7 @@ const mapStateToProps = (state: AppState, props: RouteComponentProps) => {
isTenant: selectIsTenant(state),
hasViewActivityRedactedRole: selectHasViewActivityRedactedRole(state),
hasAdminRole: selectHasAdminRole(state),
statementFingerprintInsights: selectStatementFingerprintInsights(
state,
props,
),
statementInsights: selectInsightsByFingerprint(state, props),
};
};

Expand All @@ -93,8 +90,8 @@ const mapDispatchToProps = (
refreshNodes: () => dispatch(nodesActions.refresh()),
refreshNodesLiveness: () => dispatch(nodeLivenessActions.refresh()),
refreshUserSQLRoles: () => dispatch(uiConfigActions.refreshUserSQLRoles()),
refreshStatementFingerprintInsights: (req: StmtInsightsReq) =>
dispatch(statementFingerprintInsightActions.refresh(req)),
refreshStatementInsights: (req: StmtInsightsReq) =>
dispatch(statementInsightActions.refresh(req)),
onTimeScaleChange: (ts: TimeScale) => {
dispatch(
sqlStatsActions.updateTimeScale({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { createMemoryHistory } from "history";
import Long from "long";
import { noop } from "lodash";
import * as protos from "@cockroachlabs/crdb-protobuf-client";
import { RequestError } from "src/util";
import {HexStringToInt64String, RequestError} from "src/util";
import { StatementDiagnosticsReport } from "../api";
import { cockroach } from "@cockroachlabs/crdb-protobuf-client";
import ILatencyInfo = cockroach.sql.ILatencyInfo;
Expand Down Expand Up @@ -268,6 +268,7 @@ const diagnosticsReportsInProgress: StatementDiagnosticsReport[] = [
const aggregatedTs = Date.parse("Sep 15 2021 01:00:00 GMT") * 1e-3;
const lastUpdated = moment("Sep 15 2021 01:30:00 GMT");
const aggregationInterval = 3600; // 1 hour
const stmt_fingerprint_id = HexStringToInt64String("\\x76245b7acd82d39d");

const statementsPagePropsFixture: StatementsPageProps = {
history,
Expand Down Expand Up @@ -946,6 +947,10 @@ const statementsPagePropsFixture: StatementsPageProps = {
lastReset: "2020-04-13 07:22:23",
columns: null,
isTenant: false,
insightCounts: [{
insightCount: 1,
fingerprintID: stmt_fingerprint_id,
}],
hasViewActivityRedactedRole: false,
hasAdminRole: true,
dismissAlertMessage: noop,
Expand All @@ -955,6 +960,7 @@ const statementsPagePropsFixture: StatementsPageProps = {
refreshUserSQLRoles: noop,
refreshNodes: noop,
resetSQLStats: noop,
refreshInsights: noop,
onTimeScaleChange: noop,
onActivateStatementDiagnostics: noop,
onDiagnosticsModalOpen: noop,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,15 @@ import { selectDiagnosticsReportsPerStatement } from "../store/statementDiagnost
import { AggregateStatistics } from "../statementsTable";
import { sqlStatsSelector } from "../store/sqlStats/sqlStats.selector";
import { SQLStatsState } from "../store/sqlStats";
import { localStorageSelector } from "../store/utils/selectors";
import {
adminUISelector,
localStorageSelector,
} from "../store/utils/selectors";
import { databasesListSelector } from "src/store/databasesList/databasesList.selectors";
import {
ExecutionInsightCount,
getStmtFingerprintInsightCounts,
} from "../insights";

type ICollectedStatementStatistics =
cockroach.server.serverpb.StatementsResponse.ICollectedStatementStatistics;
Expand Down Expand Up @@ -243,3 +250,10 @@ export const selectSearch = createSelector(
localStorageSelector,
localStorage => localStorage["search/StatementsPage"],
);

export const selectStatementInsightCounts = createSelector(
adminUISelector,
(state): ExecutionInsightCount[] => {
return getStmtFingerprintInsightCounts(state?.stmtInsights?.data?.results);
},
);
Loading

0 comments on commit dae5bd2

Please sign in to comment.