Skip to content

Commit 9355e10

Browse files
craig[bot]adityamarunvb
committed
107759: jobs: add button to request execution details r=maryliag a=adityamaru This is the last of the three PRs to add support for requesting, viewing and downloading execution details from the job details page. This change wires up the logic needed to request the execution details for a given job. The request is powered by the crdb_internal.request_job_execution_details builtin that triggers the collection of execution details. Fixes: cockroachdb#105076 Release note: None 107956: server: export distsender metrics from SQL pods r=knz a=nvanbenschoten This commit exports the DistSender timeseries metrics from SQL pods. ``` distsender.batches distsender.batches.partial distsender.batch_requests.replica_addressed.bytes distsender.batch_responses.replica_addressed.bytes distsender.batch_requests.cross_region.bytes distsender.batch_responses.cross_region.bytes distsender.batch_requests.cross_zone.bytes distsender.batch_responses.cross_zone.bytes distsender.batches.async.sent distsender.batches.async.throttled distsender.rpc.sent distsender.rpc.sent.local distsender.rpc.sent.nextreplicaerror distsender.errors.notleaseholder distsender.errors.inleasetransferbackoffs distsender.rangelookups requests.slow.distsender distsender.rpc.%s.sent # rpc name distsender.rpc.err.%s # error name distsender.rangefeed.total_ranges distsender.rangefeed.catchup_ranges distsender.rangefeed.error_catchup_ranges distsender.rangefeed.restart_ranges distsender.rangefeed.restart_stuck ``` Epic: None Release note: None Co-authored-by: adityamaru <[email protected]> Co-authored-by: Nathan VanBenschoten <[email protected]>
3 parents 05ad3c4 + 6ea4efb + d799d8e commit 9355e10

File tree

12 files changed

+209
-10
lines changed

12 files changed

+209
-10
lines changed

pkg/server/tenant.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,6 +1087,7 @@ func makeTenantSQLServerArgs(
10871087
TestingKnobs: dsKnobs,
10881088
}
10891089
ds := kvcoord.NewDistSender(dsCfg)
1090+
registry.AddMetricStruct(ds.Metrics())
10901091

10911092
var clientKnobs kvcoord.ClientTestingKnobs
10921093
if p, ok := baseCfg.TestingKnobs.KVClient.(*kvcoord.ClientTestingKnobs); ok {

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import { cockroach } from "@cockroachlabs/crdb-protobuf-client";
1212
import { fetchData } from "./fetchData";
13+
import { SqlExecutionRequest, executeInternalSql } from "./sqlApi";
1314

1415
export type ListJobProfilerExecutionDetailsRequest =
1516
cockroach.server.serverpb.ListJobProfilerExecutionDetailsRequest;
@@ -44,3 +45,43 @@ export const getExecutionDetailFile = (
4445
"30M",
4546
);
4647
};
48+
49+
export type CollectExecutionDetailsRequest = {
50+
job_id: Long;
51+
};
52+
53+
export type CollectExecutionDetailsResponse = {
54+
req_resp: boolean;
55+
};
56+
57+
export function collectExecutionDetails({
58+
job_id,
59+
}: CollectExecutionDetailsRequest): Promise<CollectExecutionDetailsResponse> {
60+
const args: any = [job_id.toString()];
61+
62+
const collectExecutionDetails = {
63+
sql: `SELECT crdb_internal.request_job_execution_details($1::INT) as req_resp`,
64+
arguments: args,
65+
};
66+
67+
const req: SqlExecutionRequest = {
68+
execute: true,
69+
statements: [collectExecutionDetails],
70+
};
71+
72+
return executeInternalSql<CollectExecutionDetailsResponse>(req).then(res => {
73+
// If request succeeded but query failed, throw error (caught by saga/cacheDataReducer).
74+
if (res.error) {
75+
throw res.error;
76+
}
77+
78+
if (
79+
res.execution?.txn_results[0]?.rows?.length === 0 ||
80+
res.execution?.txn_results[0]?.rows[0]["req_resp"] === false
81+
) {
82+
throw new Error("Failed to collect execution details");
83+
}
84+
85+
return res.execution.txn_results[0].rows[0];
86+
});
87+
}

pkg/ui/workspaces/cluster-ui/src/jobs/jobDetailsPage/jobDetails.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ import {
4949
import moment from "moment-timezone";
5050
import { CockroachCloudContext } from "src/contexts";
5151
import { JobProfilerView } from "./jobProfilerView";
52+
import long from "long";
53+
import { UIConfigState } from "src/store";
5254

5355
const { TabPane } = Tabs;
5456

@@ -68,13 +70,16 @@ export interface JobDetailsStateProps {
6870
onDownloadExecutionFileClicked: (
6971
req: GetJobProfilerExecutionDetailRequest,
7072
) => Promise<GetJobProfilerExecutionDetailResponse>;
73+
hasAdminRole?: UIConfigState["hasAdminRole"];
7174
}
7275

7376
export interface JobDetailsDispatchProps {
7477
refreshJob: (req: JobRequest) => void;
7578
refreshExecutionDetailFiles: (
7679
req: ListJobProfilerExecutionDetailsRequest,
7780
) => void;
81+
onRequestExecutionDetails: (jobID: long) => void;
82+
refreshUserSQLRoles: () => void;
7883
}
7984

8085
export interface JobDetailsState {
@@ -113,6 +118,7 @@ export class JobDetails extends React.Component<
113118
}
114119

115120
componentDidMount(): void {
121+
this.props.refreshUserSQLRoles();
116122
if (!this.props.jobRequest.data) {
117123
this.refresh();
118124
}
@@ -144,6 +150,7 @@ export class JobDetails extends React.Component<
144150
onDownloadExecutionFileClicked={
145151
this.props.onDownloadExecutionFileClicked
146152
}
153+
onRequestExecutionDetails={this.props.onRequestExecutionDetails}
147154
/>
148155
);
149156
};
@@ -308,11 +315,15 @@ export class JobDetails extends React.Component<
308315
<TabPane tab={TabKeysEnum.OVERVIEW} key="overview">
309316
{this.renderOverviewTabContent(hasNextRun, nextRun, job)}
310317
</TabPane>
311-
{!useContext(CockroachCloudContext) && (
312-
<TabPane tab={TabKeysEnum.PROFILER} key="advancedDebugging">
313-
{this.renderProfilerTabContent(job)}
314-
</TabPane>
315-
)}
318+
{!useContext(CockroachCloudContext) &&
319+
this.props.hasAdminRole && (
320+
<TabPane
321+
tab={TabKeysEnum.PROFILER}
322+
key="advancedDebugging"
323+
>
324+
{this.renderProfilerTabContent(job)}
325+
</TabPane>
326+
)}
316327
</Tabs>
317328
</>
318329
)}

pkg/ui/workspaces/cluster-ui/src/jobs/jobDetailsPage/jobDetailsConnected.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import { connect } from "react-redux";
1212
import { RouteComponentProps, withRouter } from "react-router-dom";
1313

14-
import { AppState } from "src/store";
14+
import { AppState, uiConfigActions } from "src/store";
1515
import {
1616
JobDetailsStateProps,
1717
JobDetailsDispatchProps,
@@ -30,6 +30,8 @@ import {
3030
actions as jobProfilerActions,
3131
} from "src/store/jobs/jobProfiler.reducer";
3232
import { Dispatch } from "redux";
33+
import long from "long";
34+
import { selectHasAdminRole } from "src/store/uiConfig";
3335

3436
const emptyState = createInitialState<JobResponse>();
3537

@@ -45,13 +47,18 @@ const mapStateToProps = (
4547
jobProfilerLastUpdated: state.adminUI?.executionDetailFiles?.lastUpdated,
4648
jobProfilerDataIsValid: state.adminUI?.executionDetailFiles?.valid,
4749
onDownloadExecutionFileClicked: getExecutionDetailFile,
50+
hasAdminRole: selectHasAdminRole(state),
4851
};
4952
};
5053

5154
const mapDispatchToProps = (dispatch: Dispatch): JobDetailsDispatchProps => ({
5255
refreshJob: (req: JobRequest) => jobActions.refresh(req),
5356
refreshExecutionDetailFiles: (req: ListJobProfilerExecutionDetailsRequest) =>
5457
dispatch(jobProfilerActions.refresh(req)),
58+
onRequestExecutionDetails: (jobID: long) => {
59+
dispatch(jobProfilerActions.collectExecutionDetails({ job_id: jobID }));
60+
},
61+
refreshUserSQLRoles: () => dispatch(uiConfigActions.refreshUserSQLRoles()),
5562
});
5663

5764
export const JobDetailsPageConnected = withRouter(

pkg/ui/workspaces/cluster-ui/src/jobs/jobDetailsPage/jobProfilerView.module.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,9 @@
2323

2424
.sorted-table {
2525
width: 100%;
26+
}
27+
28+
.gutter-row {
29+
display: flex;
30+
justify-content: right;
2631
}

pkg/ui/workspaces/cluster-ui/src/jobs/jobDetailsPage/jobProfilerView.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export type JobProfilerDispatchProps = {
5050
refreshExecutionDetailFiles: (
5151
req: ListJobProfilerExecutionDetailsRequest,
5252
) => void;
53+
onRequestExecutionDetails: (jobID: long) => void;
5354
};
5455

5556
export type JobProfilerViewProps = JobProfilerStateProps &
@@ -114,7 +115,7 @@ export function makeJobProfilerViewColumns(
114115
);
115116
onDownloadExecutionFileClicked(req).then(resp => {
116117
const type = getContentTypeForFile(executionDetailFile);
117-
const executionFileBytes = new Blob([resp.data], {
118+
const executionFileBytes = new Blob([resp?.data], {
118119
type: type,
119120
});
120121
Promise.resolve().then(() => {
@@ -143,6 +144,7 @@ export const JobProfilerView: React.FC<JobProfilerViewProps> = ({
143144
isDataValid,
144145
onDownloadExecutionFileClicked,
145146
refreshExecutionDetailFiles,
147+
onRequestExecutionDetails,
146148
}: JobProfilerViewProps) => {
147149
const columns = makeJobProfilerViewColumns(
148150
jobID,
@@ -197,7 +199,22 @@ export const JobProfilerView: React.FC<JobProfilerViewProps> = ({
197199
<>
198200
<p className={summaryCardStylesCx("summary--card__divider--large")} />
199201
<Row gutter={24}>
200-
<Col className="gutter-row" span={24}>
202+
<Col className={cx("gutter-row")} span={24}>
203+
<Button
204+
intent="secondary"
205+
onClick={() => {
206+
onRequestExecutionDetails(jobID);
207+
}}
208+
>
209+
Request Execution Details
210+
</Button>
211+
</Col>
212+
</Row>
213+
<Row gutter={24}>
214+
<Col span={24}>
215+
<p
216+
className={summaryCardStylesCx("summary--card__divider--large")}
217+
/>
201218
<SortedTable
202219
data={executionDetailFilesResponse.data?.files}
203220
columns={columns}

pkg/ui/workspaces/cluster-ui/src/store/jobs/jobProfiler.reducer.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
// licenses/APL.txt.
1010

1111
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
12-
import { DOMAIN_NAME } from "../utils";
12+
import { DOMAIN_NAME, noopReducer } from "../utils";
1313
import moment from "moment-timezone";
1414
import { createInitialState, RequestState } from "src/api/types";
1515
import {
16+
CollectExecutionDetailsRequest,
1617
ListJobProfilerExecutionDetailsRequest,
1718
ListJobProfilerExecutionDetailsResponse,
1819
} from "src/api";
@@ -58,6 +59,15 @@ const JobProfilerExecutionDetailsSlice = createSlice({
5859
) => {
5960
state.inFlight = true;
6061
},
62+
collectExecutionDetails: (
63+
_state,
64+
_action: PayloadAction<CollectExecutionDetailsRequest>,
65+
) => {},
66+
collectExecutionDetailsCompleted: noopReducer,
67+
collectExecutionDetailsFailed: (
68+
_state,
69+
_action: PayloadAction<Error>,
70+
) => {},
6171
},
6272
});
6373

pkg/ui/workspaces/cluster-ui/src/store/jobs/jobProfiler.sagas.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { actions } from "./jobProfiler.reducer";
1313
import { call, put, all, takeEvery } from "redux-saga/effects";
1414
import {
1515
ListJobProfilerExecutionDetailsRequest,
16+
collectExecutionDetails,
1617
listExecutionDetailFiles,
1718
} from "src/api";
1819

@@ -33,9 +34,24 @@ export function* requestJobProfilerSaga(
3334
}
3435
}
3536

37+
export function* collectExecutionDetailsSaga(
38+
action: ReturnType<typeof actions.collectExecutionDetails>,
39+
) {
40+
try {
41+
yield call(collectExecutionDetails, action.payload);
42+
yield put(actions.collectExecutionDetailsCompleted());
43+
// request execution details to reflect changed state for newly
44+
// requested statement.
45+
yield put(actions.request());
46+
} catch (e) {
47+
yield put(actions.collectExecutionDetailsFailed(e));
48+
}
49+
}
50+
3651
export function* jobProfilerSaga() {
3752
yield all([
3853
takeEvery(actions.refresh, refreshJobProfilerSaga),
3954
takeEvery(actions.request, requestJobProfilerSaga),
55+
takeEvery(actions.collectExecutionDetails, collectExecutionDetailsSaga),
4056
]);
4157
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2023 The Cockroach Authors.
2+
//
3+
// Use of this software is governed by the Business Source License
4+
// included in the file licenses/BSL.txt.
5+
//
6+
// As of the Change Date specified in that file, in accordance with
7+
// the Business Source License, use of this software will be governed
8+
// by the Apache License, Version 2.0, included in the file
9+
// licenses/APL.txt.
10+
11+
import { Action } from "redux";
12+
import { PayloadAction } from "src/interfaces/action";
13+
import { api as clusterUiApi } from "@cockroachlabs/cluster-ui";
14+
15+
export const COLLECT_EXECUTION_DETAILS =
16+
"cockroachui/jobs/COLLECT_EXECUTION_DETAILS";
17+
export const COLLECT_EXECUTION_DETAILS_COMPLETE =
18+
"cockroachui/jobs/COLLECT_EXECUTION_DETAILS_COMPLETE";
19+
export const COLLECT_EXECUTION_DETAILS_FAILED =
20+
"cockroachui/jobs/COLLECT_EXECUTION_DETAILS_FAILED";
21+
22+
export function collectExecutionDetailsAction(
23+
collectExecutionDetailsRequest: clusterUiApi.CollectExecutionDetailsRequest,
24+
): PayloadAction<clusterUiApi.CollectExecutionDetailsRequest> {
25+
return {
26+
type: COLLECT_EXECUTION_DETAILS,
27+
payload: collectExecutionDetailsRequest,
28+
};
29+
}
30+
31+
export function collectExecutionDetailsCompleteAction(): Action {
32+
return {
33+
type: COLLECT_EXECUTION_DETAILS_COMPLETE,
34+
};
35+
}
36+
37+
export function collectExecutionDetailsFailedAction(): Action {
38+
return {
39+
type: COLLECT_EXECUTION_DETAILS_FAILED,
40+
};
41+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2023 The Cockroach Authors.
2+
//
3+
// Use of this software is governed by the Business Source License
4+
// included in the file licenses/BSL.txt.
5+
//
6+
// As of the Change Date specified in that file, in accordance with
7+
// the Business Source License, use of this software will be governed
8+
// by the Apache License, Version 2.0, included in the file
9+
// licenses/APL.txt.
10+
11+
import { PayloadAction } from "@reduxjs/toolkit";
12+
import { refreshListExecutionDetailFiles } from "oss/src/redux/apiReducers";
13+
import { all, call, put, takeEvery } from "redux-saga/effects";
14+
import { api as clusterUiApi } from "@cockroachlabs/cluster-ui";
15+
import {
16+
COLLECT_EXECUTION_DETAILS,
17+
collectExecutionDetailsCompleteAction,
18+
collectExecutionDetailsFailedAction,
19+
} from "./jobsActions";
20+
21+
export function* collectExecutionDetailsSaga(
22+
action: PayloadAction<clusterUiApi.CollectExecutionDetailsRequest>,
23+
) {
24+
try {
25+
yield call(clusterUiApi.collectExecutionDetails, action.payload);
26+
yield put(collectExecutionDetailsCompleteAction());
27+
yield put(refreshListExecutionDetailFiles() as any);
28+
} catch (e) {
29+
yield put(collectExecutionDetailsFailedAction());
30+
}
31+
}
32+
33+
export function* jobsSaga() {
34+
yield all([
35+
takeEvery(COLLECT_EXECUTION_DETAILS, collectExecutionDetailsSaga),
36+
]);
37+
}

0 commit comments

Comments
 (0)