Skip to content

Commit

Permalink
ui: add trace rate option to stmt diagnostics
Browse files Browse the repository at this point in the history
Fixes cockroachdb#92415

Add option to select the trace rate for statement
diagnostics collection directly on the Console.

Release note (ui change): Add option to select the trace rate
for statement diagnostics collection.
  • Loading branch information
maryliag committed Apr 12, 2023
1 parent 99a4c74 commit 8842795
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 70 deletions.
2 changes: 1 addition & 1 deletion pkg/sql/stmtdiagnostics/statement_diagnostics.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ func (r *Registry) insertRequestInternal(
if minExecutionLatency == 0 {
return 0, errors.Newf(
"got non-zero sampling probability %f and empty min exec latency",
minExecutionLatency)
samplingProbability)
}
}

Expand Down
44 changes: 23 additions & 21 deletions pkg/ui/workspaces/cluster-ui/src/api/statementDiagnosticsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
// licenses/APL.txt.

import moment from "moment-timezone";
import { Duration } from "src/util/format";
import {
executeInternalSql,
SqlExecutionRequest,
Expand Down Expand Up @@ -74,7 +75,7 @@ export type InsertStmtDiagnosticRequest = {
};

export type InsertStmtDiagnosticResponse = {
stmt_diag_req_id: string;
req_resp: boolean;
};

export function createStatementDiagnosticsReport({
Expand All @@ -83,32 +84,26 @@ export function createStatementDiagnosticsReport({
minExecutionLatencySeconds,
expiresAfterSeconds,
}: InsertStmtDiagnosticRequest): Promise<InsertStmtDiagnosticResponse> {
const requestedAt = moment.now(); // milliseconds
const args: any = [stmtFingerprint, moment.utc(requestedAt).toISOString()];
const cols = ["statement_fingerprint", "requested_at"];
const args: any = [stmtFingerprint];

if (samplingProbability && samplingProbability !== 0) {
if (samplingProbability) {
args.push(samplingProbability);
cols.push("sampling_probability");
} else {
args.push(0);
}
if (minExecutionLatencySeconds && minExecutionLatencySeconds !== 0) {
args.push(minExecutionLatencySeconds.toString());
cols.push("min_execution_latency");
if (minExecutionLatencySeconds) {
args.push(Duration(minExecutionLatencySeconds * 1e9));
} else {
args.push("0");
}
if (expiresAfterSeconds && expiresAfterSeconds !== 0) {
const expiresAt = requestedAt + expiresAfterSeconds * 1000;
args.push(moment.utc(expiresAt).toISOString());
cols.push("expires_at");
args.push(Duration(expiresAfterSeconds * 1e9));
} else {
args.push("0");
}

const queryCols = cols.join(", ");
const placeHolders = args.map((elem: any, idx: number) => `$${idx + 1}`);

const createStmtDiag = {
sql: `
INSERT INTO system.statement_diagnostics_requests
(${queryCols})
VALUES (${placeHolders}) RETURNING id as stmt_diag_req_id;`,
sql: `SELECT crdb_internal.request_statement_bundle($1, $2, $3::INTERVAL, $4::INTERVAL) as req_resp`,
arguments: args,
};

Expand All @@ -124,7 +119,10 @@ export function createStatementDiagnosticsReport({
throw res.error;
}

if (res.execution?.txn_results[0]?.rows?.length === 0) {
if (
res.execution?.txn_results[0]?.rows?.length === 0 ||
res.execution?.txn_results[0]?.rows[0]["req_resp"] === false
) {
throw new Error("Failed to insert statement diagnostics request");
}

Expand Down Expand Up @@ -178,7 +176,11 @@ export type CancelStmtDiagnosticResponse = {
export function cancelStatementDiagnosticsReport({
requestId,
}: CancelStmtDiagnosticRequest): Promise<CancelStmtDiagnosticResponse> {
const query = `UPDATE system.statement_diagnostics_requests SET expires_at = '1970-01-01' WHERE completed = false AND id = $1::INT8 AND (expires_at IS NULL OR expires_at > now()) RETURNING id as stmt_diag_req_id`;
const query = `UPDATE system.statement_diagnostics_requests
SET expires_at = '1970-01-01'
WHERE completed = false
AND id = $1::INT8
AND (expires_at IS NULL OR expires_at > now()) RETURNING id as stmt_diag_req_id`;
const req: SqlExecutionRequest = {
execute: true,
statements: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import React from "react";
import { Link } from "react-router-dom";
import moment from "moment-timezone";
import classnames from "classnames/bind";
import { cockroach } from "@cockroachlabs/crdb-protobuf-client";
import { Button, Icon } from "@cockroachlabs/ui-components";
import { Button as CancelButton } from "src/button";
import { Text, TextTypes } from "src/text";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@

&__heading {
font-family: $font-family--semi-bold;
margin-bottom: $spacing-smaller;
}

&__btn-group {
Expand All @@ -48,7 +49,6 @@
}

&__radio-btn {
margin-top: $spacing-smaller;
font-family: $font-family--base;
font-weight: $font-weight--light;
}
Expand All @@ -57,6 +57,7 @@
display: flex;
flex-direction: column;
margin-left: $spacing-medium;
margin-top: $spacing-smaller;
}

&__min-latency-container {
Expand All @@ -65,6 +66,22 @@
margin-top: $spacing-smaller;
}

&__trace-container {
display: flex;
flex-direction: row;
margin-top: $spacing-smaller;
margin-bottom: $spacing-smaller;
}

&__trace-warning {
font-family: $font-family--base;
font-size: $font-size--small;
font-weight: $font-weight--light;
margin-left: $spacing-x-small;
white-space: break-spaces;
width: 240px;
}

&__expires-after-container {
margin-top: $spacing-smaller;
margin-left: $spacing-medium;
Expand All @@ -76,6 +93,12 @@
display: inline;
}

&__select-text {
font-family: $font-family--base;
font-weight: $font-weight--light;
display: block;
}

&__input {
&__min-latency-time {
width: 80px;
Expand All @@ -95,6 +118,15 @@
height: 40px;
line-height: 40px;

.ant-select-selection__rendered {
margin-top: 0;
}
}
&__trace {
width: 215px;
height: 40px;
line-height: 40px;

.ant-select-selection__rendered {
margin-top: 0;
}
Expand All @@ -104,20 +136,11 @@
&__alert {
margin-top: $spacing-small;
margin-left: $spacing-medium;
white-space: normal;
}

.ant-alert-info {
background-color: $colors--primary-blue-alert;
border: none;
}

&-icon {
margin-top: $spacing-medium-small;
}

&-message {
font-family: $font-family--base;
font-weight: $font-weight--light;
margin-left: $spacing-smaller;
}
&__warning {
margin: $spacing-xx-small 0px;
white-space: normal;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,22 @@
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

import { Alert, Button, Checkbox, Divider, Input, Radio, Select } from "antd";
import { Button, Checkbox, Divider, Input, Radio, Select } from "antd";
import "antd/lib/radio/style";
import "antd/lib/button/style";
import "antd/lib/input/style";
import "antd/lib/checkbox/style";
import "antd/lib/divider/style";
import "antd/lib/select/style";
import "antd/lib/alert/style";
import React, { useCallback, useImperativeHandle, useState } from "react";
import { Modal } from "src/modal";
import { Anchor } from "src/anchor";
import { Text } from "src/text";
import { statementDiagnostics, statementsSql } from "src/util";
import classNames from "classnames/bind";
import styles from "./activateStatementDiagnosticsModal.scss";
import { InfoCircleFilled } from "@cockroachlabs/icons";
import { InsertStmtDiagnosticRequest } from "../api";
import { InlineAlert } from "@cockroachlabs/ui-components";

const cx = classNames.bind(styles);
const { Option } = Select;
Expand All @@ -46,12 +45,13 @@ export const ActivateStatementDiagnosticsModal = React.forwardRef(
) => {
const [visible, setVisible] = useState(false);
const [statement, setStatement] = useState<string>();
const [conditional, setConditional] = useState(false);
const [conditional, setConditional] = useState(true);
const [expires, setExpires] = useState(true);
const [minExecLatency, setMinExecLatency] = useState(100);
const [minExecLatencyUnit, setMinExecLatencyUnit] =
useState("milliseconds");
const [expiresAfter, setExpiresAfter] = useState(15);
const [traceSampleRate, setTraceSampleRate] = useState(0.01);

const handleSelectChange = (value: string) => {
setMinExecLatencyUnit(value);
Expand All @@ -71,6 +71,16 @@ export const ActivateStatementDiagnosticsModal = React.forwardRef(
return numMinutes * 60; // num seconds
};

const getTraceSampleRate = (
conditional: boolean,
traceSampleRate: number,
) => {
if (conditional) {
return traceSampleRate;
}
return 0;
};

const onOkHandler = useCallback(() => {
activate({
stmtFingerprint: statement,
Expand All @@ -80,6 +90,7 @@ export const ActivateStatementDiagnosticsModal = React.forwardRef(
minExecLatencyUnit,
),
expiresAfterSeconds: getExpiresAfter(expires, expiresAfter),
samplingProbability: getTraceSampleRate(conditional, traceSampleRate),
});
setVisible(false);
}, [
Expand All @@ -90,6 +101,7 @@ export const ActivateStatementDiagnosticsModal = React.forwardRef(
minExecLatencyUnit,
expires,
expiresAfter,
traceSampleRate,
]);

const onCancelHandler = useCallback(() => setVisible(false), []);
Expand All @@ -116,29 +128,60 @@ export const ActivateStatementDiagnosticsModal = React.forwardRef(
>
<Text>
Diagnostics will be collected for the next execution that matches this{" "}
<Anchor href={statementsSql}>statement fingerprint</Anchor>, or when
the execution of the statement fingerprint exceeds a specified
latency. The request is cancelled when a single bundle is captured.{" "}
<Anchor href={statementsSql}>statement fingerprint</Anchor>, or
according to the trace and latency thresholds set below. The request
is cancelled when a single diagnostics bundle is captured.{" "}
<Anchor href={statementDiagnostics}>Learn more</Anchor>
</Text>
<div className={cx("diagnostic__options-container")}>
<Text className={cx("diagnostic__heading")}>Collect diagnostics</Text>
<Text className={cx("diagnostic__heading")}>
Collect diagnostics:
</Text>
<Radio.Group value={conditional}>
<Button.Group className={cx("diagnostic__btn-group")}>
<Radio
value={false}
className={cx("diagnostic__radio-btn")}
onChange={() => setConditional(false)}
>
On the next execution
</Radio>
<Radio
value={true}
className={cx("diagnostic__radio-btn")}
onChange={() => setConditional(true)}
>
On the next execution where the latency exceeds
Trace and collect diagnostics
<div className={cx("diagnostic__conditional-container")}>
<div className={cx("diagnostic__select-text")}>
At a sampled rate of:
</div>
<div className={cx("diagnostic__trace-container")}>
<Select
disabled={!conditional}
defaultValue={0.01}
onChange={setTraceSampleRate}
className={cx("diagnostic__select__trace")}
size="large"
>
<Option value={0.01}>1% (recommended)</Option>
<Option value={0.02}>2%</Option>
<Option value={0.03}>3%</Option>
<Option value={0.04}>4%</Option>
<Option value={0.05}>5%</Option>
<Option value={1}>100% (not recommended)</Option>
</Select>
<span className={cx("diagnostic__trace-warning")}>
We recommend starting at 1% to minimize the impact on
performance.
</span>
</div>
{getTraceSampleRate(conditional, traceSampleRate) == 1 && (
<div className={cx("diagnostic__warning")}>
<InlineAlert
intent="warning"
title="Tracing will be turned on at a 100% sampled rate until
diagnostics are collected based on the specified latency threshold
setting. This may have a significant impact on performance."
/>
</div>
)}
<div className={cx("diagnostic__select-text")}>
When the statement execution latency exceeds:
</div>
<div className={cx("diagnostic__min-latency-container")}>
<Input
type="number"
Expand All @@ -163,11 +206,18 @@ export const ActivateStatementDiagnosticsModal = React.forwardRef(
<Option value="milliseconds">milliseconds</Option>
</Select>
</div>
<Divider type="horizontal" style={{ marginBottom: 0 }} />
</div>
</Radio>
<Radio
value={false}
className={cx("diagnostic__radio-btn")}
onChange={() => setConditional(false)}
>
Trace and collect diagnostics on the next statement execution
</Radio>
</Button.Group>
</Radio.Group>
<Divider type="horizontal" />
<Checkbox checked={expires} onChange={() => setExpires(!expires)}>
<div className={cx("diagnostic__checkbox-text")}>
Diagnostics request expires after:
Expand All @@ -189,22 +239,12 @@ export const ActivateStatementDiagnosticsModal = React.forwardRef(
</div>
{conditional && !expires && (
<div className={cx("diagnostic__alert")}>
<Alert
icon={
<div className={cx("diagnostic__alert-icon")}>
<InfoCircleFilled fill="#0055FF" height={20} width={20} />
</div>
}
message={
<div className={cx("diagnostic__alert-message")}>
Executions of the same statement fingerprint will run
<InlineAlert
intent="info"
title="Executions of the same statement fingerprint will run
slower while diagnostics are activated, so it is
recommended to set an expiration time if collecting
according to a latency threshold.
</div>
}
type="info"
showIcon
according to a latency threshold."
/>
</div>
)}
Expand Down
Loading

0 comments on commit 8842795

Please sign in to comment.