Skip to content

Commit

Permalink
ui: add time picker to insights pages
Browse files Browse the repository at this point in the history
This commit adds a time picker to the workload insights overview
pages. The time picker store is shared across SQL activity and
insights components, enabling us to better correlate the insight
events with fingerprints on the SQL activity pages by time
interval.

The start and end values of the time picker (stored in the
timeScale/SQLActivity local setting) form the request to the
insights "backend", where we use these start and end values to
filter queries to the internal insights and contention tables.
The "backend" queries select events across the interval, then
partition and filter on rank, ordered by descending end time.

Part of cockroachdb#83780.

Release note (ui change): Added a time picker to the Workload
Insights Overview pages in the DB Console.
  • Loading branch information
ericharmeling committed Dec 6, 2022
1 parent 52145a4 commit 9b2571f
Show file tree
Hide file tree
Showing 29 changed files with 362 additions and 140 deletions.
94 changes: 65 additions & 29 deletions pkg/ui/workspaces/cluster-ui/src/api/insightsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,18 @@ import { FixFingerprintHexValue } from "../util";

// txnContentionQuery selects all transaction contention events that are
// above the insights latency threshold.
const txnContentionQuery = `
SELECT * FROM
function txnContentionQuery(filters?: QueryFilterFields) {
let whereClause = ` WHERE encode(waiting_txn_fingerprint_id, 'hex') != '0000000000000000'`;
if (filters?.start) {
whereClause =
whereClause + ` AND collection_ts >= '${filters.start.toISOString()}'`;
}
if (filters?.end) {
whereClause =
whereClause +
` AND (collection_ts + contention_duration) <= '${filters.end.toISOString()}'`;
}
return `SELECT * FROM
(
SELECT
waiting_txn_id,
Expand All @@ -59,12 +69,13 @@ SELECT * FROM
max(collection_ts) AS collection_ts,
sum(contention_duration) AS total_contention_duration
FROM crdb_internal.transaction_contention_events
WHERE encode(waiting_txn_fingerprint_id, 'hex') != '0000000000000000'
${whereClause}
GROUP BY waiting_txn_id, waiting_txn_fingerprint_id
)
WHERE total_contention_duration > threshold
)
WHERE rank = 1`;
}

type TransactionContentionResponseColumns = {
waiting_txn_id: string;
Expand Down Expand Up @@ -195,17 +206,17 @@ const makeInsightsSqlRequest = (queries: string[]): SqlExecutionRequest => ({
* txn contention insights and the query strings of txns involved in the contention.
* @returns a list of txn contention insights
*/
export async function getTxnInsightEvents(): Promise<
TxnContentionInsightEvent[]
> {
export async function getTxnInsightEvents(
req?: ExecutionInsightsRequest,
): Promise<TxnContentionInsightEvent[]> {
// Note that any errors encountered fetching these results are caught
// earlier in the call stack.

// Step 1: Get transaction contention events that are over the insights
// latency threshold.
const contentionResults =
await executeInternalSql<TransactionContentionResponseColumns>(
makeInsightsSqlRequest([txnContentionQuery]),
makeInsightsSqlRequest([txnContentionQuery(req)]),
);
if (sqlResultsAreEmpty(contentionResults)) {
return [];
Expand Down Expand Up @@ -651,23 +662,41 @@ function organizeExecutionInsightsResponseIntoTxns(
}

type InsightQuery<ResponseColumnType, State> = {
name: InsightNameEnum;
query: string;
toState: (response: SqlExecutionResponse<ResponseColumnType>) => State;
};

const workloadInsightsQuery: InsightQuery<
ExecutionInsightsResponseRow,
TxnInsightEvent[]
> = {
name: InsightNameEnum.highContention,
// We only surface the most recently observed problem for a given statement.
// Note that we don't filter by problem != 'None', so that we can get all
// stmts in the problematic transaction.
query: `
export type QueryFilterFields = {
stmtFingerprintId?: string;
txnFingerprintId?: string;
start?: moment.Moment;
end?: moment.Moment;
};

function workloadInsightsQuery(
filters?: QueryFilterFields,
): InsightQuery<ExecutionInsightsResponseRow, TxnInsightEvent[]> {
let whereClause = ` WHERE app_name NOT LIKE '${INTERNAL_APP_NAME_PREFIX}%'`;
if (filters?.start) {
whereClause =
whereClause + ` AND start_time >= '${filters.start.toISOString()}'`;
}
if (filters?.end) {
whereClause =
whereClause + ` AND end_time <= '${filters.end.toISOString()}'`;
}
return {
// We only surface the most recently observed problem for a given statement.
// Note that we don't filter by problem != 'None', so that we can get all
// stmts in the problematic transaction.
query: `
WITH insightsTable as (
SELECT * FROM crdb_internal.cluster_execution_insights
)
SELECT
*
FROM
crdb_internal.cluster_execution_insights
${whereClause}
)
SELECT
session_id,
insights.txn_id as txn_id,
Expand Down Expand Up @@ -696,23 +725,30 @@ SELECT
FROM
(
SELECT
txn_id,
row_number() OVER ( PARTITION BY txn_fingerprint_id ORDER BY end_time DESC ) as rank
txn_id,
row_number() OVER ( PARTITION BY txn_fingerprint_id ORDER BY end_time DESC ) as rank
FROM insightsTable
) as latestTxns
JOIN insightsTable AS insights
ON latestTxns.txn_id = insights.txn_id
WHERE latestTxns.rank = 1 AND app_name NOT LIKE '${INTERNAL_APP_NAME_PREFIX}%'
JOIN insightsTable AS insights
ON latestTxns.txn_id = insights.txn_id
WHERE latestTxns.rank = 1
`,
toState: organizeExecutionInsightsResponseIntoTxns,
};
toState: organizeExecutionInsightsResponseIntoTxns,
};
}

export type ExecutionInsights = TxnInsightEvent[];
export function getClusterInsightsApi(): Promise<ExecutionInsights> {

export type ExecutionInsightsRequest = Pick<QueryFilterFields, "start" | "end">;

export function getClusterInsightsApi(
req?: ExecutionInsightsRequest,
): Promise<ExecutionInsights> {
const insightsQuery = workloadInsightsQuery(req);
const request: SqlExecutionRequest = {
statements: [
{
sql: workloadInsightsQuery.query,
sql: insightsQuery.query,
},
],
execute: true,
Expand All @@ -721,7 +757,7 @@ export function getClusterInsightsApi(): Promise<ExecutionInsights> {
};
return executeInternalSql<ExecutionInsightsResponseRow>(request).then(
result => {
return workloadInsightsQuery.toState(result);
return insightsQuery.toState(result);
},
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import React from "react";
import { SummaryCard, SummaryCardItem } from "src/summaryCard";

import { ContendedExecution, ExecutionType } from "src/recentExecutions";
import { capitalize, Duration } from "../util";
import { capitalize, Duration, NO_DATA_FOUND } from "../util";

import { Heading } from "@cockroachlabs/ui-components";
import { ExecutionContentionTable } from "../recentExecutions/recentTransactionsTable/execContentionTable";
Expand Down Expand Up @@ -89,7 +89,7 @@ export const WaitTimeInsightsPanel: React.FC<WaitTimeInsightsPanelProps> = ({
value={
waitTime
? Duration(waitTime.asMilliseconds() * 1e6)
: "no samples"
: NO_DATA_FOUND
}
/>
{schemaName && (
Expand Down
27 changes: 24 additions & 3 deletions pkg/ui/workspaces/cluster-ui/src/insights/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
// licenses/APL.txt.

import { limitStringArray, unset } from "src/util";
import { FlattenedStmtInsights } from "src/api/insightsApi";
import {
ExecutionInsightsRequest,
FlattenedStmtInsights,
} from "src/api/insightsApi";
import {
ExecutionDetails,
FlattenedStmtInsightEvent,
Expand All @@ -28,6 +31,7 @@ import {
TxnInsightEvent,
WorkloadInsightEventFilters,
} from "./types";
import { TimeScale, toDateRange } from "../timeScaleDropdown";

export const filterTransactionInsights = (
transactions: MergedTxnInsightEvent[] | null,
Expand Down Expand Up @@ -275,7 +279,7 @@ export function getInsightsFromProblemsAndCauses(

/**
* flattenTxnInsightsToStmts flattens the txn insights array
* into its stmt insights, including the txn level ifnormation.
* into its stmt insights, including the txn level information.
* Only stmts with non-empty insights array will be included.
* @param txnInsights array of transaction insights
* @returns An array of FlattenedStmtInsightEvent where each elem
Expand All @@ -287,11 +291,18 @@ export function flattenTxnInsightsToStmts(
): FlattenedStmtInsightEvent[] {
if (!txnInsights?.length) return [];
const stmtInsights: FlattenedStmtInsightEvent[] = [];
const seenExecutions = new Set<string>();
txnInsights.forEach(txnInsight => {
const { statementInsights, ...txnInfo } = txnInsight;
statementInsights?.forEach(stmt => {
if (!stmt.insights?.length) return;
if (
!stmt.insights?.length ||
seenExecutions.has(stmt.statementExecutionID)
) {
return;
}
stmtInsights.push({ ...txnInfo, ...stmt, query: stmt.query });
seenExecutions.add(stmt.statementExecutionID);
});
});
return stmtInsights;
Expand Down Expand Up @@ -510,3 +521,13 @@ export function dedupInsights(insights: Insight[]): Insight[] {
return deduped;
}, []);
}

export function executionInsightsRequestFromTimeScale(
ts: TimeScale,
): ExecutionInsightsRequest {
const [startTime, endTime] = toDateRange(ts);
return {
start: startTime,
end: endTime,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { AppState } from "src/store";
import {
actions as statementInsights,
selectStatementInsightDetails,
selectStatementInsightsError,
selectExecutionInsightsError,
} from "src/store/insights/statementInsights";
import { selectIsTenant } from "src/store/uiConfig";
import { TimeScale } from "../../timeScaleDropdown";
Expand All @@ -30,7 +30,7 @@ const mapStateToProps = (
props: RouteComponentProps,
): StatementInsightDetailsStateProps => {
const insightStatements = selectStatementInsightDetails(state, props);
const insightError = selectStatementInsightsError(state);
const insightError = selectExecutionInsightsError(state);
return {
insightEventDetails: insightStatements,
insightError: insightError,
Expand All @@ -42,7 +42,7 @@ const mapDispatchToProps = (
dispatch: Dispatch,
): StatementInsightDetailsDispatchProps => ({
refreshStatementInsights: () => {
dispatch(statementInsights.refresh());
dispatch(statementInsights.refresh({}));
},
setTimeScale: (ts: TimeScale) => {
dispatch(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { SummaryCard, SummaryCardItem } from "src/summaryCard";
import { DATE_WITH_SECONDS_AND_MILLISECONDS_FORMAT_24_UTC } from "src/util/format";
import { WaitTimeInsightsLabels } from "src/detailsPanels/waitTimeInsightsPanel";
import { TxnContentionInsightDetailsRequest } from "src/api";
import { NO_DATA_FOUND } from "src/util";
import {
InsightsSortedTable,
makeInsightsColumns,
Expand Down Expand Up @@ -102,10 +103,10 @@ export const TransactionInsightDetailsOverviewTab: React.FC<Props> = ({

const rowsRead =
stmtInsights?.reduce((count, stmt) => (count += stmt.rowsRead), 0) ??
"no samples";
NO_DATA_FOUND;
const rowsWritten =
stmtInsights?.reduce((count, stmt) => (count += stmt.rowsWritten), 0) ??
"no samples";
NO_DATA_FOUND;

return (
<div>
Expand All @@ -125,21 +126,21 @@ export const TransactionInsightDetailsOverviewTab: React.FC<Props> = ({
value={
insightDetails.startTime?.format(
DATE_WITH_SECONDS_AND_MILLISECONDS_FORMAT_24_UTC,
) ?? "no samples"
) ?? NO_DATA_FOUND
}
/>
<SummaryCardItem label="Rows Read" value={rowsRead} />
<SummaryCardItem label="Rows Written" value={rowsWritten} />
<SummaryCardItem
label="Priority"
value={insightDetails.priority ?? "no samples"}
value={insightDetails.priority ?? NO_DATA_FOUND}
/>
<SummaryCardItem
label="Full Scan"
value={
insightDetails.statementInsights
?.some(stmt => stmt.isFullScan)
?.toString() ?? "no samples"
?.toString() ?? NO_DATA_FOUND
}
/>
</SummaryCard>
Expand All @@ -148,7 +149,7 @@ export const TransactionInsightDetailsOverviewTab: React.FC<Props> = ({
<SummaryCard>
<SummaryCardItem
label="Number of Retries"
value={insightDetails.retries ?? "no samples"}
value={insightDetails.retries ?? NO_DATA_FOUND}
/>
{insightDetails.lastRetryReason && (
<SummaryCardItem
Expand All @@ -158,7 +159,7 @@ export const TransactionInsightDetailsOverviewTab: React.FC<Props> = ({
)}
<SummaryCardItem
label="Session ID"
value={insightDetails.sessionID ?? "no samples"}
value={insightDetails.sessionID ?? NO_DATA_FOUND}
/>
<SummaryCardItem
label="Application"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
DATE_WITH_SECONDS_AND_MILLISECONDS_FORMAT,
Duration,
limitText,
NO_DATA_FOUND,
} from "src/util";
import { InsightExecEnum, FlattenedStmtInsightEvent } from "src/insights";
import {
Expand Down Expand Up @@ -143,7 +144,7 @@ export function makeStatementInsightsColumns(
title: insightsTableTitles.contention(execType),
cell: (item: FlattenedStmtInsightEvent) =>
!item.totalContentionTime
? "no samples"
? NO_DATA_FOUND
: Duration(item.totalContentionTime.asMilliseconds() * 1e6),
sort: (item: FlattenedStmtInsightEvent) =>
item.totalContentionTime?.asMilliseconds() ?? -1,
Expand Down
Loading

0 comments on commit 9b2571f

Please sign in to comment.