From ca2fbe4d24356fd0f49eadfb2e96b1133c493d1d Mon Sep 17 00:00:00 2001 From: j82w Date: Thu, 22 Sep 2022 13:57:28 +0000 Subject: [PATCH] ui: insights transaction details support multiple blocking transactions closes #88264 Release justification: Category 2: Bug fixes and low-risk updates to new functionality Release note: (ui change): Add support for multiple blocking transaction on insights transaction details page. Merged the cards into the table, and fixed the total contention time. --- .../cluster-ui/src/api/insightsApi.ts | 124 +++++++++++++----- .../cluster-ui/src/insights/types.ts | 30 +++-- .../insightDetailsTables.tsx | 36 ++++- .../transactionInsightDetails.tsx | 45 +++---- .../workloadInsights/util/insightsColumns.tsx | 26 ++++ 5 files changed, 182 insertions(+), 79 deletions(-) diff --git a/pkg/ui/workspaces/cluster-ui/src/api/insightsApi.ts b/pkg/ui/workspaces/cluster-ui/src/api/insightsApi.ts index 67cf23d6c969..30611bc15ee1 100644 --- a/pkg/ui/workspaces/cluster-ui/src/api/insightsApi.ts +++ b/pkg/ui/workspaces/cluster-ui/src/api/insightsApi.ts @@ -10,13 +10,14 @@ import { executeInternalSql, - SqlExecutionRequest, - SqlExecutionResponse, INTERNAL_SQL_API_APP, - LONG_TIMEOUT, LARGE_RESULT_SIZE, + LONG_TIMEOUT, + SqlExecutionRequest, + SqlExecutionResponse, } from "./sqlApi"; import { + BlockedContentionDetails, InsightExecEnum, InsightNameEnum, StatementInsightEvent, @@ -57,22 +58,25 @@ const txnContentionQuery = `SELECT * FROM (SELECT waiting_txn_id, encode( waiting_txn_fingerprint_id, 'hex' - ) AS waiting_txn_fingerprint_id, + ) AS waiting_txn_fingerprint_id, collection_ts, - contention_duration, - row_number() over ( - PARTITION BY waiting_txn_fingerprint_id - ORDER BY - collection_ts DESC - ) AS rank, - threshold + total_contention_duration AS contention_duration, + row_number() over ( + PARTITION BY waiting_txn_fingerprint_id + ORDER BY + collection_ts DESC + ) AS rank, threshold FROM (SELECT "sql.insights.latency_threshold" :: INTERVAL AS threshold FROM [SHOW CLUSTER SETTING sql.insights.latency_threshold]), - (SELECT DISTINCT ON (waiting_txn_id) * - FROM crdb_internal.transaction_contention_events tce), + (SELECT waiting_txn_id, + waiting_txn_fingerprint_id, + max(collection_ts) AS collection_ts, + sum(contention_duration) AS total_contention_duration + FROM crdb_internal.transaction_contention_events tce + GROUP BY waiting_txn_id, waiting_txn_fingerprint_id), (SELECT txn_id FROM crdb_internal.cluster_execution_insights) - WHERE contention_duration > threshold + WHERE total_contention_duration > threshold OR waiting_txn_id = txn_id) WHERE rank = 1`; @@ -352,24 +356,47 @@ type TxnContentionDetailsResponseColumns = { function transactionContentionDetailsResultsToEventState( response: SqlExecutionResponse, ): TransactionContentionEventDetailsResponse { - if (!response.execution.txn_results[0].rows) { + const resultsRows = response.execution.txn_results[0].rows; + if (!resultsRows) { // No data. return; } - const row = response.execution.txn_results[0].rows[0]; + + const blockingContentionDetails = new Array( + resultsRows.length, + ); + + let totalContentionTime = 0; + resultsRows.forEach((value, idx) => { + const contentionTimeInMs = moment + .duration(value.contention_duration) + .asMilliseconds(); + totalContentionTime += contentionTimeInMs; + blockingContentionDetails[idx] = { + blockingExecutionID: value.blocking_txn_id, + blockingFingerprintID: value.blocking_txn_fingerprint_id, + blockingQueries: null, + collectionTimeStamp: moment(value.collection_ts), + contentionTimeMs: contentionTimeInMs, + contendedKey: value.key, + schemaName: value.schema_name, + databaseName: value.database_name, + tableName: value.table_name, + indexName: + value.index_name && value.index_name !== "" + ? value.index_name + : "primary index", + }; + }); + + const row = resultsRows[0]; return { executionID: row.waiting_txn_id, fingerprintID: row.waiting_txn_fingerprint_id, startTime: moment(row.collection_ts), - elapsedTime: moment.duration(row.contention_duration).asMilliseconds(), + totalContentionTime: totalContentionTime, + blockingContentionDetails: blockingContentionDetails, contentionThreshold: moment.duration(row.threshold).asMilliseconds(), - blockingExecutionID: row.blocking_txn_id, - blockingFingerprintID: row.blocking_txn_fingerprint_id, - schemaName: row.schema_name, - databaseName: row.database_name, - tableName: row.table_name, - indexName: row.index_name, - contendedKey: row.key, insightName: InsightNameEnum.highContention, execType: InsightExecEnum.TRANSACTION, }; @@ -425,9 +452,13 @@ export function getTransactionInsightEventDetailsState( return executeInternalSql( waitingFingerprintStmtsRequest, ).then(waitingTxnStmtQueries => { - const blockingTxnFingerprintId = - contentionResults.execution.txn_results[0].rows[0] - .blocking_txn_fingerprint_id; + let blockingTxnFingerprintId: string[] = []; + contentionResults.execution.txn_results.forEach(txnResult => { + blockingTxnFingerprintId = blockingTxnFingerprintId.concat( + txnResult.rows.map(x => x.blocking_txn_fingerprint_id), + ); + }); + const blockingTxnFingerprintRequest: SqlExecutionRequest = { statements: [ { @@ -441,9 +472,17 @@ export function getTransactionInsightEventDetailsState( return executeInternalSql( blockingTxnFingerprintRequest, ).then(blockingTxnStmtFingerprintIDs => { - const blockingStmtFingerprintIDs = - blockingTxnStmtFingerprintIDs.execution.txn_results[0].rows[0] - .query_ids; + let blockingStmtFingerprintIDs: string[] = []; + blockingTxnStmtFingerprintIDs.execution.txn_results[0].rows.map( + row => { + if (row.query_ids && row.query_ids.length > 0) { + blockingStmtFingerprintIDs = blockingStmtFingerprintIDs.concat( + row.query_ids, + ); + } + }, + ); + const blockingFingerprintStmtsRequest: SqlExecutionRequest = { statements: [ { @@ -492,6 +531,25 @@ export function combineTransactionInsightEventDetailsState( waitingFingerprintStmtState && blockingFingerprintStmtState ) { + txnContentionDetailsState.blockingContentionDetails.forEach(blockedRow => { + const currBlockedFingerprintStmts = blockingTxnFingerprintState.filter( + x => x.fingerprintID === blockedRow.blockingFingerprintID, + ); + if ( + !currBlockedFingerprintStmts || + currBlockedFingerprintStmts.length != 1 + ) { + return; + } + + blockedRow.blockingQueries = currBlockedFingerprintStmts[0].queryIDs.map( + id => + blockingFingerprintStmtState.find( + stmt => stmt.stmtFingerprintID === id, + )?.query, + ); + }); + res = { ...txnContentionDetailsState, application: waitingTxnFingerprintState[0].application, @@ -501,12 +559,6 @@ export function combineTransactionInsightEventDetailsState( stmt => stmt.stmtFingerprintID === id, )?.query, ), - blockingQueries: blockingTxnFingerprintState[0].queryIDs.map( - id => - blockingFingerprintStmtState.find( - stmt => stmt.stmtFingerprintID === id, - )?.query, - ), }; } return res; diff --git a/pkg/ui/workspaces/cluster-ui/src/insights/types.ts b/pkg/ui/workspaces/cluster-ui/src/insights/types.ts index b8f456c6b830..a6f3ad69f6a9 100644 --- a/pkg/ui/workspaces/cluster-ui/src/insights/types.ts +++ b/pkg/ui/workspaces/cluster-ui/src/insights/types.ts @@ -38,15 +38,8 @@ export type TransactionInsightEvent = { execType: InsightExecEnum; }; -export type TransactionInsightEventDetails = { - executionID: string; - queries: string[]; - insights: Insight[]; - startTime: Moment; - elapsedTime: number; - contentionThreshold: number; - application: string; - fingerprintID: string; +export type BlockedContentionDetails = { + collectionTimeStamp: Moment; blockingExecutionID: string; blockingFingerprintID: string; blockingQueries: string[]; @@ -55,6 +48,19 @@ export type TransactionInsightEventDetails = { databaseName: string; tableName: string; indexName: string; + contentionTimeMs: number; +}; + +export type TransactionInsightEventDetails = { + executionID: string; + queries: string[]; + insights: Insight[]; + startTime: Moment; + totalContentionTime: number; + contentionThreshold: number; + application: string; + fingerprintID: string; + blockingContentionDetails: BlockedContentionDetails[]; execType: InsightExecEnum; }; @@ -97,7 +103,11 @@ export type EventExecution = { fingerprintID: string; queries: string[]; startTime: Moment; - elapsedTime: number; + contentionTimeMs: number; + schemaName: string; + databaseName: string; + tableName: string; + indexName: string; execType: InsightExecEnum; }; diff --git a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsightDetails/insightDetailsTables.tsx b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsightDetails/insightDetailsTables.tsx index 901834f8261f..eec85d95e76e 100644 --- a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsightDetails/insightDetailsTables.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsightDetails/insightDetailsTables.tsx @@ -42,16 +42,40 @@ export function makeInsightDetailsColumns( sort: (item: EventExecution) => item.queries.length, }, { - name: "startTime", - title: insightsTableTitles.startTime(execType), + name: "contentionStartTime", + title: insightsTableTitles.contentionStartTime(execType), cell: (item: EventExecution) => item.startTime.format(DATE_FORMAT), sort: (item: EventExecution) => item.startTime.unix(), }, { - name: "elapsedTime", - title: insightsTableTitles.elapsedTime(execType), - cell: (item: EventExecution) => Duration(item.elapsedTime * 1e6), - sort: (item: EventExecution) => item.elapsedTime, + name: "contention", + title: insightsTableTitles.contention(execType), + cell: (item: EventExecution) => Duration(item.contentionTimeMs * 1e6), + sort: (item: EventExecution) => item.contentionTimeMs, + }, + { + name: "schemaName", + title: insightsTableTitles.schemaName(execType), + cell: (item: EventExecution) => item.schemaName, + sort: (item: EventExecution) => item.schemaName, + }, + { + name: "databaseName", + title: insightsTableTitles.databaseName(execType), + cell: (item: EventExecution) => item.databaseName, + sort: (item: EventExecution) => item.databaseName, + }, + { + name: "tableName", + title: insightsTableTitles.tableName(execType), + cell: (item: EventExecution) => item.tableName, + sort: (item: EventExecution) => item.tableName, + }, + { + name: "indexName", + title: insightsTableTitles.indexName(execType), + cell: (item: EventExecution) => item.indexName, + sort: (item: EventExecution) => item.indexName, }, ]; } diff --git a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsightDetails/transactionInsightDetails.tsx b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsightDetails/transactionInsightDetails.tsx index 6487ed54f322..8e2394b913c1 100644 --- a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsightDetails/transactionInsightDetails.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsightDetails/transactionInsightDetails.tsx @@ -15,17 +15,13 @@ import { Heading } from "@cockroachlabs/ui-components"; import { Col, Row } from "antd"; import "antd/lib/col/style"; import "antd/lib/row/style"; -import moment from "moment"; import { Button } from "src/button"; import { Loading } from "src/loading"; import { SqlBox, SqlBoxSize } from "src/sql"; import { SummaryCard, SummaryCardItem } from "src/summaryCard"; import { DATE_FORMAT_24_UTC } from "src/util/format"; import { getMatchParamByName } from "src/util/query"; -import { - WaitTimeInsightsLabels, - WaitTimeInsightsPanel, -} from "src/detailsPanels/waitTimeInsightsPanel"; +import { WaitTimeInsightsLabels } from "src/detailsPanels/waitTimeInsightsPanel"; import { TransactionInsightEventDetailsRequest, TransactionInsightEventDetailsResponse, @@ -107,7 +103,7 @@ export class TransactionInsightDetails extends React.Component { + return { + executionID: x.blockingExecutionID, + fingerprintID: x.blockingFingerprintID, + queries: x.blockingQueries, + startTime: x.collectionTimeStamp, + contentionTimeMs: x.contentionTimeMs, + execType: insightDetails.execType, + schemaName: x.schemaName, + databaseName: x.databaseName, + tableName: x.tableName, + indexName: x.indexName, + }; + }); + return ( <>
@@ -164,17 +166,6 @@ export class TransactionInsightDetails extends React.Component
- diff --git a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/util/insightsColumns.tsx b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/util/insightsColumns.tsx index f87cb658eed6..230ff4c35b2b 100644 --- a/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/util/insightsColumns.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/insights/workloadInsights/util/insightsColumns.tsx @@ -27,7 +27,12 @@ export const insightsColumnLabels = { numRetries: "Retries", isFullScan: "Full Scan", contention: "Contention Time", + contentionStartTime: "Contention Start Time (UTC)", rowsProcessed: "Rows Processed", + schemaName: "Schema Name", + databaseName: "Database Name", + tableName: "Table Name", + indexName: "Index Name", }; export type InsightsTableColumnKeys = keyof typeof insightsColumnLabels; @@ -99,6 +104,12 @@ export const insightsTableTitles: InsightsTableTitleType = { "startTime", ); }, + contentionStartTime: (execType: InsightExecEnum) => { + return makeToolTip( +

The timestamp at which contention was detected for the {execType}.

, + "contentionStartTime", + ); + }, elapsedTime: (execType: InsightExecEnum) => { return makeToolTip(

The time elapsed since the {execType} started execution.

, @@ -111,6 +122,21 @@ export const insightsTableTitles: InsightsTableTitleType = { "username", ); }, + schemaName: (execType: InsightExecEnum) => { + return makeToolTip(

The name of the contended schema.

, "schemaName"); + }, + databaseName: (execType: InsightExecEnum) => { + return makeToolTip( +

The name of the contended database.

, + "databaseName", + ); + }, + tableName: (execType: InsightExecEnum) => { + return makeToolTip(

The name of the contended table.

, "tableName"); + }, + indexName: (execType: InsightExecEnum) => { + return makeToolTip(

The name of the contended index.

, "indexName"); + }, applicationName: (execType: InsightExecEnum) => { return makeToolTip(

The name of the application that ran the {execType}.

,