Skip to content

Commit

Permalink
ui: new plan table on statement details
Browse files Browse the repository at this point in the history
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 cockroachdb#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.
  • Loading branch information
maryliag committed Mar 10, 2022
1 parent c985a38 commit cb4b682
Show file tree
Hide file tree
Showing 7 changed files with 334 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Original file line number Diff line number Diff line change
@@ -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<PlanHashStats | null>(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 (
<PlansSortedTable
columns={columns}
data={plans}
className="statements-table"
/>
);
}

function renderExplainPlan(
plan: PlanHashStats,
backToPlanTable: () => void,
): React.ReactElement {
return (
<div>
<Helmet title="Plan Details" />
<Button
onClick={backToPlanTable}
type="unstyled-link"
size="small"
icon={<ArrowLeft fontSize={"10px"} />}
iconPosition="left"
className="small-margin"
>
All Plans
</Button>
<SqlBox value={plan.explain_plan} />
</div>
);
}
Original file line number Diff line number Diff line change
@@ -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<PlanHashStats> {}

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 (
<Tooltip
style="tableTitle"
placement="bottom"
content={"The ID of the Plan."}
>
{planDetailsColumnLabels.planID}
</Tooltip>
);
},
last_exec_time: () => {
return (
<Tooltip
style="tableTitle"
placement="bottom"
content={"The last time this Plan was executed."}
>
{planDetailsColumnLabels.last_exec_time}
</Tooltip>
);
},
avg_exec_time: () => {
return (
<Tooltip
style="tableTitle"
placement="bottom"
content={"The average execution time for this Plan."}
>
{planDetailsColumnLabels.avg_exec_time}
</Tooltip>
);
},
exec_count: () => {
return (
<Tooltip
style="tableTitle"
placement="bottom"
content={"The execution count for this Plan."}
>
{planDetailsColumnLabels.exec_count}
</Tooltip>
);
},
avg_rows_read: () => {
return (
<Tooltip
style="tableTitle"
placement="bottom"
content={"The average of rows read by this Plan."}
>
{planDetailsColumnLabels.avg_rows_read}
</Tooltip>
);
},
};

export function makeExplainPlanColumns(
handleDetails: (plan: PlanHashStats) => void,
): ColumnDescriptor<PlanHashStats>[] {
const duration = (v: number) => Duration(v * 1e9);
return [
{
name: "planID",
title: planDetailsTableTitles.planID(),
cell: (item: PlanHashStats) => (
<a onClick={() => handleDetails(item)}>{longToInt(item.plan_hash)}</a>
),
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),
},
];
}
Original file line number Diff line number Diff line change
Expand Up @@ -536,8 +536,8 @@ export const getStatementDetailsPropsFixture = (): StatementDetailsProps => ({
statements_per_plan_hash: [
{
stats: {
count: new Long(5),
first_attempt_count: new Long(5),
count: new Long(3),
first_attempt_count: new Long(3),
max_retries: new Long(0),
legacy_last_err: "",
legacy_last_err_redacted: "",
Expand Down Expand Up @@ -628,6 +628,100 @@ export const getStatementDetailsPropsFixture = (): StatementDetailsProps => ({
explain_plan: "• virtual table\n table: @primary",
plan_hash: new Long(14192395335876201826),
},
{
stats: {
count: new Long(2),
first_attempt_count: new Long(2),
max_retries: new Long(0),
legacy_last_err: "",
legacy_last_err_redacted: "",
num_rows: {
mean: 6,
squared_diffs: 0,
},
parse_lat: {
mean: 0.0000876,
squared_diffs: 2.35792e-8,
},
plan_lat: {
mean: 0.008131,
squared_diffs: 0.00127640837,
},
run_lat: {
mean: 0.0002796,
squared_diffs: 2.401919999999999e-8,
},
service_lat: {
mean: 0.008522,
squared_diffs: 0.001298238058,
},
overhead_lat: {
mean: 0.000023799999999999972,
squared_diffs: 5.492799999999973e-9,
},
sensitive_info: {
last_err: "",
most_recent_plan_description: {
name: "virtual table",
attrs: [
{
key: "Table",
value: "node_build_info@primary",
},
],
children: [],
},
most_recent_plan_timestamp: {
seconds: new Long(1614851546),
nanos: 956814000,
},
},
bytes_read: {
mean: 0,
squared_diffs: 0,
},
rows_read: {
mean: 0,
squared_diffs: 0,
},
rows_written: {
mean: 0,
squared_diffs: 0,
},
exec_stats: {
count: new Long(5),
network_bytes: {
mean: 0,
squared_diffs: 0,
},
max_mem_usage: {
mean: 10240,
squared_diffs: 0,
},
contention_time: {
mean: 0,
squared_diffs: 0,
},
network_messages: {
mean: 0,
squared_diffs: 0,
},
max_disk_usage: {
mean: 0,
squared_diffs: 0,
},
},
sql_type: "TypeDML",
last_exec_timestamp: {
seconds: Long.fromInt(1599670292),
nanos: 111613000,
},
nodes: [new Long(1)],
plan_gists: ["Ah0GAg=="],
},
explain_plan: "• virtual table\n table: @primary\nFULL SCAN",
plan_hash: new Long(14192395335876212345),
},
],
internal_app_name_prefix: "$ internal",
toJSON: () => ({}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -776,13 +775,13 @@ export class StatementDetails extends React.Component<
</TabPane>
)}
<TabPane tab="Explain Plan" key="explain-plan">
<SummaryCard>
<PlanView
title="Explain Plan"
plan={explainPlan}
globalProperties={explainGlobalProps}
/>
</SummaryCard>
<Row gutter={24}>
<Col className="gutter-row" span={24}>
<SqlBox value={formatted_query} />
</Col>
</Row>
<p className={summaryCardStylesCx("summary--card__divider")} />
<PlanDetails plans={statements_per_plan_hash} />
</TabPane>
<TabPane
tab="Execution Stats"
Expand Down
Loading

0 comments on commit cb4b682

Please sign in to comment.