Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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: #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]>
  • Loading branch information
3 people committed Aug 2, 2023
3 parents 05ad3c4 + 6ea4efb + d799d8e commit 9355e10
Show file tree
Hide file tree
Showing 12 changed files with 209 additions and 10 deletions.
1 change: 1 addition & 0 deletions pkg/server/tenant.go
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,7 @@ func makeTenantSQLServerArgs(
TestingKnobs: dsKnobs,
}
ds := kvcoord.NewDistSender(dsCfg)
registry.AddMetricStruct(ds.Metrics())

var clientKnobs kvcoord.ClientTestingKnobs
if p, ok := baseCfg.TestingKnobs.KVClient.(*kvcoord.ClientTestingKnobs); ok {
Expand Down
41 changes: 41 additions & 0 deletions pkg/ui/workspaces/cluster-ui/src/api/jobProfilerApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

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

export type ListJobProfilerExecutionDetailsRequest =
cockroach.server.serverpb.ListJobProfilerExecutionDetailsRequest;
Expand Down Expand Up @@ -44,3 +45,43 @@ export const getExecutionDetailFile = (
"30M",
);
};

export type CollectExecutionDetailsRequest = {
job_id: Long;
};

export type CollectExecutionDetailsResponse = {
req_resp: boolean;
};

export function collectExecutionDetails({
job_id,
}: CollectExecutionDetailsRequest): Promise<CollectExecutionDetailsResponse> {
const args: any = [job_id.toString()];

const collectExecutionDetails = {
sql: `SELECT crdb_internal.request_job_execution_details($1::INT) as req_resp`,
arguments: args,
};

const req: SqlExecutionRequest = {
execute: true,
statements: [collectExecutionDetails],
};

return executeInternalSql<CollectExecutionDetailsResponse>(req).then(res => {
// If request succeeded but query failed, throw error (caught by saga/cacheDataReducer).
if (res.error) {
throw res.error;
}

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

return res.execution.txn_results[0].rows[0];
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ import {
import moment from "moment-timezone";
import { CockroachCloudContext } from "src/contexts";
import { JobProfilerView } from "./jobProfilerView";
import long from "long";
import { UIConfigState } from "src/store";

const { TabPane } = Tabs;

Expand All @@ -68,13 +70,16 @@ export interface JobDetailsStateProps {
onDownloadExecutionFileClicked: (
req: GetJobProfilerExecutionDetailRequest,
) => Promise<GetJobProfilerExecutionDetailResponse>;
hasAdminRole?: UIConfigState["hasAdminRole"];
}

export interface JobDetailsDispatchProps {
refreshJob: (req: JobRequest) => void;
refreshExecutionDetailFiles: (
req: ListJobProfilerExecutionDetailsRequest,
) => void;
onRequestExecutionDetails: (jobID: long) => void;
refreshUserSQLRoles: () => void;
}

export interface JobDetailsState {
Expand Down Expand Up @@ -113,6 +118,7 @@ export class JobDetails extends React.Component<
}

componentDidMount(): void {
this.props.refreshUserSQLRoles();
if (!this.props.jobRequest.data) {
this.refresh();
}
Expand Down Expand Up @@ -144,6 +150,7 @@ export class JobDetails extends React.Component<
onDownloadExecutionFileClicked={
this.props.onDownloadExecutionFileClicked
}
onRequestExecutionDetails={this.props.onRequestExecutionDetails}
/>
);
};
Expand Down Expand Up @@ -308,11 +315,15 @@ export class JobDetails extends React.Component<
<TabPane tab={TabKeysEnum.OVERVIEW} key="overview">
{this.renderOverviewTabContent(hasNextRun, nextRun, job)}
</TabPane>
{!useContext(CockroachCloudContext) && (
<TabPane tab={TabKeysEnum.PROFILER} key="advancedDebugging">
{this.renderProfilerTabContent(job)}
</TabPane>
)}
{!useContext(CockroachCloudContext) &&
this.props.hasAdminRole && (
<TabPane
tab={TabKeysEnum.PROFILER}
key="advancedDebugging"
>
{this.renderProfilerTabContent(job)}
</TabPane>
)}
</Tabs>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import { connect } from "react-redux";
import { RouteComponentProps, withRouter } from "react-router-dom";

import { AppState } from "src/store";
import { AppState, uiConfigActions } from "src/store";
import {
JobDetailsStateProps,
JobDetailsDispatchProps,
Expand All @@ -30,6 +30,8 @@ import {
actions as jobProfilerActions,
} from "src/store/jobs/jobProfiler.reducer";
import { Dispatch } from "redux";
import long from "long";
import { selectHasAdminRole } from "src/store/uiConfig";

const emptyState = createInitialState<JobResponse>();

Expand All @@ -45,13 +47,18 @@ const mapStateToProps = (
jobProfilerLastUpdated: state.adminUI?.executionDetailFiles?.lastUpdated,
jobProfilerDataIsValid: state.adminUI?.executionDetailFiles?.valid,
onDownloadExecutionFileClicked: getExecutionDetailFile,
hasAdminRole: selectHasAdminRole(state),
};
};

const mapDispatchToProps = (dispatch: Dispatch): JobDetailsDispatchProps => ({
refreshJob: (req: JobRequest) => jobActions.refresh(req),
refreshExecutionDetailFiles: (req: ListJobProfilerExecutionDetailsRequest) =>
dispatch(jobProfilerActions.refresh(req)),
onRequestExecutionDetails: (jobID: long) => {
dispatch(jobProfilerActions.collectExecutionDetails({ job_id: jobID }));
},
refreshUserSQLRoles: () => dispatch(uiConfigActions.refreshUserSQLRoles()),
});

export const JobDetailsPageConnected = withRouter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@

.sorted-table {
width: 100%;
}

.gutter-row {
display: flex;
justify-content: right;
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export type JobProfilerDispatchProps = {
refreshExecutionDetailFiles: (
req: ListJobProfilerExecutionDetailsRequest,
) => void;
onRequestExecutionDetails: (jobID: long) => void;
};

export type JobProfilerViewProps = JobProfilerStateProps &
Expand Down Expand Up @@ -114,7 +115,7 @@ export function makeJobProfilerViewColumns(
);
onDownloadExecutionFileClicked(req).then(resp => {
const type = getContentTypeForFile(executionDetailFile);
const executionFileBytes = new Blob([resp.data], {
const executionFileBytes = new Blob([resp?.data], {
type: type,
});
Promise.resolve().then(() => {
Expand Down Expand Up @@ -143,6 +144,7 @@ export const JobProfilerView: React.FC<JobProfilerViewProps> = ({
isDataValid,
onDownloadExecutionFileClicked,
refreshExecutionDetailFiles,
onRequestExecutionDetails,
}: JobProfilerViewProps) => {
const columns = makeJobProfilerViewColumns(
jobID,
Expand Down Expand Up @@ -197,7 +199,22 @@ export const JobProfilerView: React.FC<JobProfilerViewProps> = ({
<>
<p className={summaryCardStylesCx("summary--card__divider--large")} />
<Row gutter={24}>
<Col className="gutter-row" span={24}>
<Col className={cx("gutter-row")} span={24}>
<Button
intent="secondary"
onClick={() => {
onRequestExecutionDetails(jobID);
}}
>
Request Execution Details
</Button>
</Col>
</Row>
<Row gutter={24}>
<Col span={24}>
<p
className={summaryCardStylesCx("summary--card__divider--large")}
/>
<SortedTable
data={executionDetailFilesResponse.data?.files}
columns={columns}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
// licenses/APL.txt.

import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { DOMAIN_NAME } from "../utils";
import { DOMAIN_NAME, noopReducer } from "../utils";
import moment from "moment-timezone";
import { createInitialState, RequestState } from "src/api/types";
import {
CollectExecutionDetailsRequest,
ListJobProfilerExecutionDetailsRequest,
ListJobProfilerExecutionDetailsResponse,
} from "src/api";
Expand Down Expand Up @@ -58,6 +59,15 @@ const JobProfilerExecutionDetailsSlice = createSlice({
) => {
state.inFlight = true;
},
collectExecutionDetails: (
_state,
_action: PayloadAction<CollectExecutionDetailsRequest>,
) => {},
collectExecutionDetailsCompleted: noopReducer,
collectExecutionDetailsFailed: (
_state,
_action: PayloadAction<Error>,
) => {},
},
});

Expand Down
16 changes: 16 additions & 0 deletions pkg/ui/workspaces/cluster-ui/src/store/jobs/jobProfiler.sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { actions } from "./jobProfiler.reducer";
import { call, put, all, takeEvery } from "redux-saga/effects";
import {
ListJobProfilerExecutionDetailsRequest,
collectExecutionDetails,
listExecutionDetailFiles,
} from "src/api";

Expand All @@ -33,9 +34,24 @@ export function* requestJobProfilerSaga(
}
}

export function* collectExecutionDetailsSaga(
action: ReturnType<typeof actions.collectExecutionDetails>,
) {
try {
yield call(collectExecutionDetails, action.payload);
yield put(actions.collectExecutionDetailsCompleted());
// request execution details to reflect changed state for newly
// requested statement.
yield put(actions.request());
} catch (e) {
yield put(actions.collectExecutionDetailsFailed(e));
}
}

export function* jobProfilerSaga() {
yield all([
takeEvery(actions.refresh, refreshJobProfilerSaga),
takeEvery(actions.request, requestJobProfilerSaga),
takeEvery(actions.collectExecutionDetails, collectExecutionDetailsSaga),
]);
}
41 changes: 41 additions & 0 deletions pkg/ui/workspaces/db-console/src/redux/jobs/jobsActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2023 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 { Action } from "redux";
import { PayloadAction } from "src/interfaces/action";
import { api as clusterUiApi } from "@cockroachlabs/cluster-ui";

export const COLLECT_EXECUTION_DETAILS =
"cockroachui/jobs/COLLECT_EXECUTION_DETAILS";
export const COLLECT_EXECUTION_DETAILS_COMPLETE =
"cockroachui/jobs/COLLECT_EXECUTION_DETAILS_COMPLETE";
export const COLLECT_EXECUTION_DETAILS_FAILED =
"cockroachui/jobs/COLLECT_EXECUTION_DETAILS_FAILED";

export function collectExecutionDetailsAction(
collectExecutionDetailsRequest: clusterUiApi.CollectExecutionDetailsRequest,
): PayloadAction<clusterUiApi.CollectExecutionDetailsRequest> {
return {
type: COLLECT_EXECUTION_DETAILS,
payload: collectExecutionDetailsRequest,
};
}

export function collectExecutionDetailsCompleteAction(): Action {
return {
type: COLLECT_EXECUTION_DETAILS_COMPLETE,
};
}

export function collectExecutionDetailsFailedAction(): Action {
return {
type: COLLECT_EXECUTION_DETAILS_FAILED,
};
}
37 changes: 37 additions & 0 deletions pkg/ui/workspaces/db-console/src/redux/jobs/jobsSagas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2023 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 { PayloadAction } from "@reduxjs/toolkit";
import { refreshListExecutionDetailFiles } from "oss/src/redux/apiReducers";
import { all, call, put, takeEvery } from "redux-saga/effects";
import { api as clusterUiApi } from "@cockroachlabs/cluster-ui";
import {
COLLECT_EXECUTION_DETAILS,
collectExecutionDetailsCompleteAction,
collectExecutionDetailsFailedAction,
} from "./jobsActions";

export function* collectExecutionDetailsSaga(
action: PayloadAction<clusterUiApi.CollectExecutionDetailsRequest>,
) {
try {
yield call(clusterUiApi.collectExecutionDetails, action.payload);
yield put(collectExecutionDetailsCompleteAction());
yield put(refreshListExecutionDetailFiles() as any);
} catch (e) {
yield put(collectExecutionDetailsFailedAction());
}
}

export function* jobsSaga() {
yield all([
takeEvery(COLLECT_EXECUTION_DETAILS, collectExecutionDetailsSaga),
]);
}
2 changes: 2 additions & 0 deletions pkg/ui/workspaces/db-console/src/redux/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ import { sessionsSaga } from "./sessions";
import { sqlStatsSaga } from "./sqlStats";
import { indexUsageStatsSaga } from "./indexUsageStats";
import { timeScaleSaga } from "src/redux/timeScale";
import { jobsSaga } from "./jobs/jobsSagas";

export default function* rootSaga() {
yield all([
fork(queryMetricsSaga),
fork(localSettingsSaga),
fork(customAnalyticsSaga),
fork(statementsSaga),
fork(jobsSaga),
fork(analyticsSaga),
fork(sessionsSaga),
fork(sqlStatsSaga),
Expand Down
Loading

0 comments on commit 9355e10

Please sign in to comment.