From 676bbf27363ad7b8c2cd49122301572acf81418f Mon Sep 17 00:00:00 2001 From: Marylia Gutierrez Date: Thu, 10 Mar 2022 13:40:12 -0500 Subject: [PATCH] ui: new plan table on statement details Previously, the Explain Plan tab on Statement Details was showing only one plan. This commit introduces a table of plan with their respective executions stats. When a plan is clicked on the table, it shows the Plan and its statistics. Fixes #72129 Release justification: Category 4 Release note (ui change): Explain Plan tab on Statement Details shows statistics for all the plans executed by the selected statement on the selected period. --- .../src/sql/sqlhighlight.module.scss | 2 +- .../src/statementDetails/planDetails/index.ts | 11 ++ .../planDetails/planDetails.tsx | 76 ++++++++++ .../planDetails/plansTable.tsx | 140 ++++++++++++++++++ .../src/statementDetails/statementDetails.tsx | 21 ++- .../src/statsTableUtil/statsTableUtil.tsx | 1 - 6 files changed, 238 insertions(+), 13 deletions(-) create mode 100644 pkg/ui/workspaces/cluster-ui/src/statementDetails/planDetails/index.ts create mode 100644 pkg/ui/workspaces/cluster-ui/src/statementDetails/planDetails/planDetails.tsx create mode 100644 pkg/ui/workspaces/cluster-ui/src/statementDetails/planDetails/plansTable.tsx diff --git a/pkg/ui/workspaces/cluster-ui/src/sql/sqlhighlight.module.scss b/pkg/ui/workspaces/cluster-ui/src/sql/sqlhighlight.module.scss index 820715c912d6..cffe2188c45b 100644 --- a/pkg/ui/workspaces/cluster-ui/src/sql/sqlhighlight.module.scss +++ b/pkg/ui/workspaces/cluster-ui/src/sql/sqlhighlight.module.scss @@ -27,7 +27,7 @@ font-family: SFMono-Semibold; font-size: 14px; line-height: 1.57; - white-space: pre-line; + white-space: pre; word-wrap: break-word; span { diff --git a/pkg/ui/workspaces/cluster-ui/src/statementDetails/planDetails/index.ts b/pkg/ui/workspaces/cluster-ui/src/statementDetails/planDetails/index.ts new file mode 100644 index 000000000000..36a26ec6363c --- /dev/null +++ b/pkg/ui/workspaces/cluster-ui/src/statementDetails/planDetails/index.ts @@ -0,0 +1,11 @@ +// Copyright 2022 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +export * from "./planDetails"; diff --git a/pkg/ui/workspaces/cluster-ui/src/statementDetails/planDetails/planDetails.tsx b/pkg/ui/workspaces/cluster-ui/src/statementDetails/planDetails/planDetails.tsx new file mode 100644 index 000000000000..2603f5f14590 --- /dev/null +++ b/pkg/ui/workspaces/cluster-ui/src/statementDetails/planDetails/planDetails.tsx @@ -0,0 +1,76 @@ +// Copyright 2022 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +import React, { useState } from "react"; +import { Helmet } from "react-helmet"; +import { ArrowLeft } from "@cockroachlabs/icons"; +import { + PlansSortedTable, + makeExplainPlanColumns, + PlanHashStats, +} from "./plansTable"; +import { Button } from "../../button"; +import { SqlBox } from "../../sql"; + +interface PlanDetailsProps { + plans: PlanHashStats[]; +} + +export function PlanDetails({ plans }: PlanDetailsProps): React.ReactElement { + const [plan, setPlan] = useState(null); + const handleDetails = (plan: PlanHashStats): void => { + setPlan(plan); + }; + const backToPlanTable = (): void => { + setPlan(null); + }; + + if (plan) { + return renderExplainPlan(plan, backToPlanTable); + } else { + return renderPlanTable(plans, handleDetails); + } +} + +function renderPlanTable( + plans: PlanHashStats[], + handleDetails: (plan: PlanHashStats) => void, +): React.ReactElement { + const columns = makeExplainPlanColumns(handleDetails); + return ( + + ); +} + +function renderExplainPlan( + plan: PlanHashStats, + backToPlanTable: () => void, +): React.ReactElement { + return ( +
+ + + +
+ ); +} diff --git a/pkg/ui/workspaces/cluster-ui/src/statementDetails/planDetails/plansTable.tsx b/pkg/ui/workspaces/cluster-ui/src/statementDetails/planDetails/plansTable.tsx new file mode 100644 index 000000000000..5885bd59581b --- /dev/null +++ b/pkg/ui/workspaces/cluster-ui/src/statementDetails/planDetails/plansTable.tsx @@ -0,0 +1,140 @@ +// Copyright 2022 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +import React from "react"; +import { ColumnDescriptor, SortedTable } from "src/sortedtable"; +import { Tooltip } from "@cockroachlabs/ui-components"; +import { cockroach } from "@cockroachlabs/crdb-protobuf-client"; +import { + Duration, + formatNumberForDisplay, + longToInt, + TimestampToMoment, +} from "../../util"; + +export type PlanHashStats = cockroach.server.serverpb.StatementDetailsResponse.ICollectedStatementGroupedByPlanHash; +export class PlansSortedTable extends SortedTable {} + +const planDetailsColumnLabels = { + planID: "Plan ID", + last_exec_time: "Last Execution Time", + avg_exec_time: "Average Execution Time", + exec_count: "Execution Count", + avg_rows_read: "Average Rows Read", +}; +export type PlanDetailsTableColumnKeys = keyof typeof planDetailsColumnLabels; + +type PlanDetailsTableTitleType = { + [key in PlanDetailsTableColumnKeys]: () => JSX.Element; +}; + +export const planDetailsTableTitles: PlanDetailsTableTitleType = { + planID: () => { + return ( + + {planDetailsColumnLabels.planID} + + ); + }, + last_exec_time: () => { + return ( + + {planDetailsColumnLabels.last_exec_time} + + ); + }, + avg_exec_time: () => { + return ( + + {planDetailsColumnLabels.avg_exec_time} + + ); + }, + exec_count: () => { + return ( + + {planDetailsColumnLabels.exec_count} + + ); + }, + avg_rows_read: () => { + return ( + + {planDetailsColumnLabels.avg_rows_read} + + ); + }, +}; + +export function makeExplainPlanColumns( + handleDetails: (plan: PlanHashStats) => void, +): ColumnDescriptor[] { + const duration = (v: number) => Duration(v * 1e9); + return [ + { + name: "planID", + title: planDetailsTableTitles.planID(), + cell: (item: PlanHashStats) => ( + handleDetails(item)}>{longToInt(item.plan_hash)} + ), + sort: (item: PlanHashStats) => longToInt(item.plan_hash), + alwaysShow: true, + }, + { + name: "last_exec_time", + title: planDetailsTableTitles.last_exec_time(), + cell: (item: PlanHashStats) => + TimestampToMoment(item.stats.last_exec_timestamp).format( + "MMM DD, YYYY HH:MM", + ), + sort: (item: PlanHashStats) => + TimestampToMoment(item.stats.last_exec_timestamp).unix(), + }, + { + name: "avg_exec_time", + title: planDetailsTableTitles.avg_exec_time(), + cell: (item: PlanHashStats) => + formatNumberForDisplay(item.stats.run_lat.mean, duration), + sort: (item: PlanHashStats) => item.stats.run_lat.mean, + }, + { + name: "exec_count", + title: planDetailsTableTitles.exec_count(), + cell: (item: PlanHashStats) => longToInt(item.stats.count), + sort: (item: PlanHashStats) => longToInt(item.stats.count), + }, + { + name: "avg_rows_read", + title: planDetailsTableTitles.avg_rows_read(), + cell: (item: PlanHashStats) => longToInt(item.stats.rows_read.mean), + sort: (item: PlanHashStats) => longToInt(item.stats.rows_read.mean), + }, + ]; +} diff --git a/pkg/ui/workspaces/cluster-ui/src/statementDetails/statementDetails.tsx b/pkg/ui/workspaces/cluster-ui/src/statementDetails/statementDetails.tsx index 83967cec098c..25e77855dec3 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementDetails/statementDetails.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementDetails/statementDetails.tsx @@ -41,7 +41,7 @@ import { Button } from "src/button"; import { SqlBox } from "src/sql"; import { SortSetting } from "src/sortedtable"; import { Tooltip } from "@cockroachlabs/ui-components"; -import { PlanView } from "./planView"; +import { PlanDetails } from "./planDetails"; import { SummaryCard } from "src/summaryCard"; import { latencyBreakdown, @@ -444,6 +444,7 @@ export class StatementDetails extends React.Component< hasViewActivityRedactedRole, } = this.props; const { currentTab } = this.state; + const { statements_per_plan_hash } = this.props.statementDetails; const { stats, app_names, @@ -500,9 +501,7 @@ export class StatementDetails extends React.Component< const regions = unique( (stats.nodes || []).map(node => nodeRegions[node.toString()]), ).sort(); - const explainPlan = - stats.sensitive_info && stats.sensitive_info.most_recent_plan_description; - const explainGlobalProps = { distribution: distSQL, vectorized: vec }; + const duration = (v: number) => Duration(v * 1e9); const hasDiagnosticReports = diagnosticsReports.length > 0; const lastExec = @@ -776,13 +775,13 @@ export class StatementDetails extends React.Component< )} - - - + + + + + +

+