Skip to content

Commit d58f436

Browse files
committed
ui: add transaction diagnostics api client and page
This change introduces a transaction diagnostics request page that lists oustanding and completed requests along with the transaction IDs or fingerprints. If the bundle has not yet been captured, we can only show the transactionId, otherwise we'll have the fingeprint available to display as well. In both cases, the ID or fingerprint will link to the corresponding SQL Activity page for that Transaction. This change also converts StatementDiagnosticsHistory into a functional component which uses `swr` for loading data. This change adds a diagnostics history page with tabs for Statements and Transactions that switches between the components above. Resolves: CRDB-53543 Epic: CRDB-53541 Release note (db console change): The statement diagnostics page in Advanced Debug now also displays any active transaction diagnostics requests in a separate tab Release note: None
1 parent 755c186 commit d58f436

File tree

14 files changed

+1383
-148
lines changed

14 files changed

+1383
-148
lines changed

pkg/ui/pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/ui/workspaces/cluster-ui/src/api/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
export * from "./fetchData";
77
export * from "./statementDiagnosticsApi";
8+
export * from "./transactionDiagnosticsApi";
89
export * from "./statementsApi";
910
export * from "./basePath";
1011
export * from "./nodesApi";
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Copyright 2025 The Cockroach Authors.
2+
//
3+
// Use of this software is governed by the CockroachDB Software License
4+
// included in the /LICENSE file.
5+
6+
import { cockroach } from "@cockroachlabs/crdb-protobuf-client";
7+
import Long from "long";
8+
import moment from "moment-timezone";
9+
10+
import { fetchData } from "src/api";
11+
12+
import { DurationToMomentDuration, NumberToDuration } from "../util";
13+
14+
const TRANSACTION_DIAGNOSTICS_PATH = "_status/txndiagreports";
15+
const CANCEL_TRANSACTION_DIAGNOSTICS_PATH =
16+
TRANSACTION_DIAGNOSTICS_PATH + "/cancel";
17+
18+
export type TransactionDiagnosticsReport = {
19+
id: string;
20+
transaction_fingerprint: string;
21+
transaction_fingerprint_id: bigint;
22+
statement_fingerprint_ids: Uint8Array[];
23+
completed: boolean;
24+
transaction_diagnostics_id?: string;
25+
requested_at: moment.Moment;
26+
min_execution_latency?: moment.Duration;
27+
expires_at?: moment.Moment;
28+
sampling_probability?: number;
29+
redacted: boolean;
30+
username: string;
31+
};
32+
33+
export type TransactionDiagnosticsResponse = TransactionDiagnosticsReport[];
34+
35+
export async function getTransactionDiagnosticsReports(): Promise<TransactionDiagnosticsResponse> {
36+
const response = await fetchData(
37+
cockroach.server.serverpb.TransactionDiagnosticsReportsResponse,
38+
TRANSACTION_DIAGNOSTICS_PATH,
39+
);
40+
return response.reports.map(report => {
41+
const minExecutionLatency = report.min_execution_latency
42+
? DurationToMomentDuration(report.min_execution_latency)
43+
: null;
44+
let txnId = BigInt(0);
45+
if (
46+
report.transaction_fingerprint_id &&
47+
report.transaction_fingerprint_id.length >= 8
48+
) {
49+
// Note: the probuf code constructs the Uint8Array fingeprint
50+
// using a shared underlying buffer so we need to use its
51+
// specific offset when decoding the Uint64 within.
52+
const dv = new DataView(report.transaction_fingerprint_id.buffer);
53+
txnId = dv.getBigUint64(report.transaction_fingerprint_id.byteOffset);
54+
}
55+
return {
56+
id: report.id.toString(),
57+
transaction_fingerprint: report.transaction_fingerprint,
58+
transaction_fingerprint_id: txnId,
59+
statement_fingerprint_ids: report.statement_fingerprint_ids,
60+
completed: report.completed,
61+
transaction_diagnostics_id: report.transaction_diagnostics_id?.toString(),
62+
requested_at: moment.unix(report.requested_at?.seconds.toNumber()),
63+
min_execution_latency: minExecutionLatency,
64+
expires_at: moment.unix(report.expires_at?.seconds.toNumber()),
65+
sampling_probability: report.sampling_probability,
66+
redacted: report.redacted,
67+
username: report.username,
68+
};
69+
});
70+
}
71+
72+
export type InsertTxnDiagnosticRequest = {
73+
transactionFingerprintId: Uint8Array;
74+
statementFingerprintIds: Uint8Array[];
75+
samplingProbability?: number;
76+
minExecutionLatencySeconds?: number;
77+
expiresAfterSeconds?: number;
78+
redacted: boolean;
79+
};
80+
81+
export type InsertTxnDiagnosticResponse = {
82+
req_resp: boolean;
83+
};
84+
85+
export async function createTransactionDiagnosticsReport(
86+
req: InsertTxnDiagnosticRequest,
87+
): Promise<InsertTxnDiagnosticResponse> {
88+
return fetchData(
89+
cockroach.server.serverpb.CreateTransactionDiagnosticsReportResponse,
90+
TRANSACTION_DIAGNOSTICS_PATH,
91+
cockroach.server.serverpb.CreateTransactionDiagnosticsReportRequest,
92+
new cockroach.server.serverpb.CreateTransactionDiagnosticsReportRequest({
93+
transaction_fingerprint_id: req.transactionFingerprintId,
94+
statement_fingerprint_ids: req.statementFingerprintIds,
95+
sampling_probability: req.samplingProbability,
96+
min_execution_latency: NumberToDuration(req.minExecutionLatencySeconds),
97+
expires_at: NumberToDuration(req.expiresAfterSeconds),
98+
redacted: req.redacted,
99+
}),
100+
).then(response => {
101+
return {
102+
req_resp: response.report !== null,
103+
};
104+
});
105+
}
106+
107+
export type CancelTxnDiagnosticRequest = {
108+
requestId: string;
109+
};
110+
111+
export type CancelTxnDiagnosticResponse = {
112+
txn_diag_req_id: string;
113+
};
114+
115+
export async function cancelTransactionDiagnosticsReport(
116+
req: CancelTxnDiagnosticRequest,
117+
): Promise<CancelTxnDiagnosticResponse> {
118+
return fetchData(
119+
cockroach.server.serverpb.CancelTransactionDiagnosticsReportResponse,
120+
CANCEL_TRANSACTION_DIAGNOSTICS_PATH,
121+
cockroach.server.serverpb.CancelTransactionDiagnosticsReportRequest,
122+
new cockroach.server.serverpb.CancelTransactionDiagnosticsReportRequest({
123+
request_id: Long.fromString(req.requestId),
124+
}),
125+
).then(response => {
126+
if (response.error) {
127+
throw new Error(response.error);
128+
}
129+
return {
130+
txn_diag_req_id: req.requestId,
131+
};
132+
});
133+
}

pkg/ui/workspaces/cluster-ui/src/statementDetails/diagnostics/diagnosticsUtils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import { TimeScale, toDateRange } from "src/timeScaleDropdown";
1010

1111
import { StatementDiagnosticsReport } from "../../api";
1212

13-
export function getDiagnosticsStatus(
14-
diagnosticsRequest: StatementDiagnosticsReport,
15-
): DiagnosticStatuses {
13+
export function getDiagnosticsStatus(diagnosticsRequest: {
14+
completed: boolean;
15+
}): DiagnosticStatuses {
1616
if (diagnosticsRequest.completed) {
1717
return "READY";
1818
}

pkg/ui/workspaces/db-console/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"redux-saga": "^1.1.3",
7373
"redux-thunk": "^2.3.0",
7474
"reselect": "^4.0.0",
75+
"swr": "2.2.5",
7576
"uplot": "^1.6.19",
7677
"whatwg-fetch": "^2.0.3"
7778
},

pkg/ui/workspaces/db-console/src/app.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import { ConnectedDecommissionedNodeHistory } from "src/views/reports";
6666
import Certificates from "src/views/reports/containers/certificates";
6767
import CustomChart from "src/views/reports/containers/customChart";
6868
import Debug from "src/views/reports/containers/debug";
69+
import DiagnosticsHistoryPage from "src/views/reports/containers/diagnosticsHistoryPage";
6970
import EnqueueRange from "src/views/reports/containers/enqueueRange";
7071
import HotRanges from "src/views/reports/containers/hotranges";
7172
import Localities from "src/views/reports/containers/localities";
@@ -75,7 +76,6 @@ import ProblemRanges from "src/views/reports/containers/problemRanges";
7576
import Range from "src/views/reports/containers/range";
7677
import ReduxDebug from "src/views/reports/containers/redux";
7778
import Settings from "src/views/reports/containers/settings";
78-
import StatementsDiagnosticsHistoryView from "src/views/reports/containers/statementDiagnosticsHistory";
7979
import Stores from "src/views/reports/containers/stores";
8080
import ScheduleDetails from "src/views/schedules/scheduleDetails";
8181
import SchedulesPage from "src/views/schedules/schedulesPage";
@@ -467,8 +467,14 @@ export const App: React.FC<AppProps> = (props: AppProps) => {
467467
/>
468468
<Route
469469
exact
470-
path={`/reports/statements/diagnosticshistory`}
471-
component={StatementsDiagnosticsHistoryView}
470+
path={`/reports/diagnosticshistory`}
471+
component={DiagnosticsHistoryPage}
472+
/>
473+
{/* Redirect old statement diagnostics route to new tabbed page */}
474+
<Redirect
475+
exact
476+
from={`/reports/statements/diagnosticshistory`}
477+
to={`/reports/diagnosticshistory?tab=Statements`}
472478
/>
473479
{/* hot ranges */}
474480
<Route

pkg/ui/workspaces/db-console/src/views/reports/containers/debug/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,9 +229,9 @@ function StatementDiagnosticsSelector(props: {
229229
return (
230230
canSeeDebugPanelLink && (
231231
<DebugPanelLink
232-
name="Statement Diagnostics History"
233-
url="#/reports/statements/diagnosticshistory"
234-
note="View the history of statement diagnostics requests"
232+
name="Diagnostics History"
233+
url="#/reports/diagnosticshistory"
234+
note="View the history of statement and transaction diagnostics requests"
235235
/>
236236
)
237237
);

0 commit comments

Comments
 (0)