From 6e6a4d181020f20c3d1d5549df922581e96d1a99 Mon Sep 17 00:00:00 2001 From: Xin Hao Zhang Date: Tue, 8 Mar 2022 14:55:04 -0500 Subject: [PATCH 1/4] cluster-ui: add active stmts and txns pages for CC This commit adds the active statements and active transactions pages required by cockroach cloud. The components created in this commit are intended for use in `managed-service`. Previously, we poll for updated sessions informaation every 30 minutes. This commit changes the frequency to every 10s. Release note: None --- .../activeStatementDetails.selectors.ts | 30 ++ .../activeStatementDetailsConnected.tsx | 43 +++ .../cluster-ui/src/statementDetails/index.ts | 1 + .../activeStatementsPage.selectors.ts | 90 +++++ .../statementsPageConnected.tsx | 328 ++++++++++-------- .../localStorage/localStorage.reducer.ts | 33 ++ .../src/store/sessions/sessions.reducer.ts | 4 + .../src/store/sessions/sessions.sagas.ts | 6 +- .../activeTransactionDetails.selectors.tsx | 30 ++ .../activeTransactionDetailsConnected.tsx | 43 +++ .../src/transactionDetails/index.ts | 1 + .../activeTransactionsPage.selectors.tsx | 92 +++++ .../transactionsPageConnected.tsx | 193 +++++++---- 13 files changed, 669 insertions(+), 225 deletions(-) create mode 100644 pkg/ui/workspaces/cluster-ui/src/statementDetails/activeStatementDetails.selectors.ts create mode 100644 pkg/ui/workspaces/cluster-ui/src/statementDetails/activeStatementDetailsConnected.tsx create mode 100644 pkg/ui/workspaces/cluster-ui/src/statementsPage/activeStatementsPage.selectors.ts create mode 100644 pkg/ui/workspaces/cluster-ui/src/transactionDetails/activeTransactionDetails.selectors.tsx create mode 100644 pkg/ui/workspaces/cluster-ui/src/transactionDetails/activeTransactionDetailsConnected.tsx create mode 100644 pkg/ui/workspaces/cluster-ui/src/transactionsPage/activeTransactionsPage.selectors.tsx diff --git a/pkg/ui/workspaces/cluster-ui/src/statementDetails/activeStatementDetails.selectors.ts b/pkg/ui/workspaces/cluster-ui/src/statementDetails/activeStatementDetails.selectors.ts new file mode 100644 index 000000000000..44e5a9eeb833 --- /dev/null +++ b/pkg/ui/workspaces/cluster-ui/src/statementDetails/activeStatementDetails.selectors.ts @@ -0,0 +1,30 @@ +// 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 { createSelector } from "reselect"; +import { AppState } from "src"; +import { match, RouteComponentProps } from "react-router-dom"; +import { getMatchParamByName } from "src/util/query"; +import { executionIdAttr } from "../util/constants"; +import { getActiveStatementsFromSessions } from "../activeExecutions/activeStatementUtils"; + +export const selectActiveStatement = createSelector( + (_: AppState, props: RouteComponentProps) => props.match, + (state: AppState) => state.adminUI.sessions, + (match: match, response) => { + if (!response.data) return null; + + const executionID = getMatchParamByName(match, executionIdAttr); + return getActiveStatementsFromSessions( + response.data, + response.lastUpdated, + ).find(stmt => stmt.executionID === executionID); + }, +); diff --git a/pkg/ui/workspaces/cluster-ui/src/statementDetails/activeStatementDetailsConnected.tsx b/pkg/ui/workspaces/cluster-ui/src/statementDetails/activeStatementDetailsConnected.tsx new file mode 100644 index 000000000000..d494c636bb36 --- /dev/null +++ b/pkg/ui/workspaces/cluster-ui/src/statementDetails/activeStatementDetailsConnected.tsx @@ -0,0 +1,43 @@ +// 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 { RouteComponentProps, withRouter } from "react-router-dom"; +import { connect } from "react-redux"; +import { Dispatch } from "redux"; +import { actions as sessionsActions } from "src/store/sessions"; +import { AppState } from "../store"; +import { + ActiveStatementDetails, + ActiveStatementDetailsDispatchProps, +} from "./activeStatementDetails"; +import { selectActiveStatement } from "./activeStatementDetails.selectors"; +import { ActiveStatementDetailsStateProps } from "."; + +// For tenant cases, we don't show information about node, regions and +// diagnostics. +const mapStateToProps = ( + state: AppState, + props: RouteComponentProps, +): ActiveStatementDetailsStateProps => { + return { + statement: selectActiveStatement(state, props), + match: props.match, + }; +}; + +const mapDispatchToProps = ( + dispatch: Dispatch, +): ActiveStatementDetailsDispatchProps => ({ + refreshSessions: () => dispatch(sessionsActions.refresh()), +}); + +export const ActiveStatementDetailsPageConnected = withRouter( + connect(mapStateToProps, mapDispatchToProps)(ActiveStatementDetails), +); diff --git a/pkg/ui/workspaces/cluster-ui/src/statementDetails/index.ts b/pkg/ui/workspaces/cluster-ui/src/statementDetails/index.ts index 38950537e677..2876da41b8ff 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementDetails/index.ts +++ b/pkg/ui/workspaces/cluster-ui/src/statementDetails/index.ts @@ -14,3 +14,4 @@ export * from "./diagnostics/diagnosticsUtils"; export * from "./planView"; export * from "./statementDetailsConnected"; export * from "./activeStatementDetails"; +export * from "./activeStatementDetailsConnected"; diff --git a/pkg/ui/workspaces/cluster-ui/src/statementsPage/activeStatementsPage.selectors.ts b/pkg/ui/workspaces/cluster-ui/src/statementsPage/activeStatementsPage.selectors.ts new file mode 100644 index 000000000000..0ad4e8c13e49 --- /dev/null +++ b/pkg/ui/workspaces/cluster-ui/src/statementsPage/activeStatementsPage.selectors.ts @@ -0,0 +1,90 @@ +// 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 { createSelector } from "reselect"; +import { getActiveStatementsFromSessions } from "../activeExecutions/activeStatementUtils"; +import { localStorageSelector } from "./statementsPage.selectors"; +import { + ActiveStatementFilters, + ActiveStatementsViewDispatchProps, + ActiveStatementsViewStateProps, + AppState, + SortSetting, +} from "src"; +import { actions as sessionsActions } from "src/store/sessions"; +import { actions as localStorageActions } from "src/store/localStorage"; +import { Dispatch } from "redux"; + +export const selectActiveStatements = createSelector( + (state: AppState) => state.adminUI.sessions, + response => { + if (!response.data) return []; + + return getActiveStatementsFromSessions(response.data, response.lastUpdated); + }, +); + +export const selectSortSetting = (state: AppState): SortSetting => + localStorageSelector(state)["sortSetting/ActiveStatementsPage"]; + +export const selectFilters = (state: AppState): ActiveStatementFilters => + localStorageSelector(state)["filters/ActiveStatementsPage"]; + +const selectLocalStorageColumns = (state: AppState) => { + const localStorage = localStorageSelector(state); + return localStorage["showColumns/ActiveStatementsPage"]; +}; + +export const selectColumns = createSelector( + selectLocalStorageColumns, + value => { + if (value == null) return null; + + return value.split(",").map(col => col.trim()); + }, +); + +export const mapStateToActiveStatementsPageProps = ( + state: AppState, +): ActiveStatementsViewStateProps => ({ + statements: selectActiveStatements(state), + sessionsError: state.adminUI.sessions.lastError, + selectedColumns: selectColumns(state), + sortSetting: selectSortSetting(state), + filters: selectFilters(state), +}); + +export const mapDispatchToActiveStatementsPageProps = ( + dispatch: Dispatch, +): ActiveStatementsViewDispatchProps => ({ + refreshSessions: () => dispatch(sessionsActions.refresh()), + onColumnsSelect: columns => { + dispatch( + localStorageActions.update({ + key: "showColumns/ActiveStatementsPage", + value: columns.join(","), + }), + ); + }, + onFiltersChange: (filters: ActiveStatementFilters) => + dispatch( + localStorageActions.update({ + key: "filters/ActiveStatementsPage", + value: filters, + }), + ), + onSortChange: (ss: SortSetting) => + dispatch( + localStorageActions.update({ + key: "sortSetting/ActiveStatementsPage", + value: ss, + }), + ), +}); diff --git a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPageConnected.tsx b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPageConnected.tsx index 516e8ee2b9a3..17ffdec62218 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPageConnected.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPageConnected.tsx @@ -18,9 +18,7 @@ import { actions as analyticsActions } from "src/store/analytics"; import { actions as localStorageActions } from "src/store/localStorage"; import { actions as sqlStatsActions } from "src/store/sqlStats"; import { - StatementsPage, StatementsPageDispatchProps, - StatementsPageProps, StatementsPageStateProps, } from "./statementsPage"; import { @@ -44,6 +42,18 @@ import { nodeRegionsByIDSelector } from "../store/nodes"; import { StatementsRequest } from "src/api/statementsApi"; import { TimeScale } from "../timeScaleDropdown"; import { cockroach, google } from "@cockroachlabs/crdb-protobuf-client"; +import { + StatementsPageRoot, + StatementsPageRootProps, +} from "./statementsPageRoot"; +import { + ActiveStatementsViewDispatchProps, + ActiveStatementsViewStateProps, +} from "./activeStatementsView"; +import { + mapDispatchToActiveStatementsPageProps, + mapStateToActiveStatementsPageProps, +} from "./activeStatementsPage.selectors"; type IStatementDiagnosticsReport = cockroach.server.serverpb.IStatementDiagnosticsReport; type IDuration = google.protobuf.IDuration; @@ -54,170 +64,200 @@ const CreateStatementDiagnosticsReportRequest = const CancelStatementDiagnosticsReportRequest = cockroach.server.serverpb.CancelStatementDiagnosticsReportRequest; +type StateProps = { + fingerprintsPageProps: StatementsPageStateProps & RouteComponentProps; + activePageProps: ActiveStatementsViewStateProps; +}; + +type DispatchProps = { + fingerprintsPageProps: StatementsPageDispatchProps; + activePageProps: ActiveStatementsViewDispatchProps; +}; + export const ConnectedStatementsPage = withRouter( connect< - StatementsPageStateProps, - StatementsPageDispatchProps, - RouteComponentProps + StateProps, + DispatchProps, + RouteComponentProps, + StatementsPageRootProps >( - (state: AppState, props: StatementsPageProps) => ({ - apps: selectApps(state), - columns: selectColumns(state), - databases: selectDatabases(state), - timeScale: selectTimeScale(state), - filters: selectFilters(state), - isTenant: selectIsTenant(state), - hasViewActivityRedactedRole: selectHasViewActivityRedactedRole(state), - lastReset: selectLastReset(state), - nodeRegions: selectIsTenant(state) ? {} : nodeRegionsByIDSelector(state), - search: selectSearch(state), - sortSetting: selectSortSetting(state), - statements: selectStatements(state, props), - statementsError: selectStatementsLastError(state), - totalFingerprints: selectTotalFingerprints(state), + (state: AppState, props: RouteComponentProps) => ({ + fingerprintsPageProps: { + ...props, + apps: selectApps(state), + columns: selectColumns(state), + databases: selectDatabases(state), + timeScale: selectTimeScale(state), + filters: selectFilters(state), + isTenant: selectIsTenant(state), + hasViewActivityRedactedRole: selectHasViewActivityRedactedRole(state), + lastReset: selectLastReset(state), + nodeRegions: selectIsTenant(state) + ? {} + : nodeRegionsByIDSelector(state), + search: selectSearch(state), + sortSetting: selectSortSetting(state), + statements: selectStatements(state, props), + statementsError: selectStatementsLastError(state), + totalFingerprints: selectTotalFingerprints(state), + }, + activePageProps: mapStateToActiveStatementsPageProps(state), }), (dispatch: Dispatch) => ({ - refreshStatements: (req: StatementsRequest) => - dispatch(sqlStatsActions.refresh(req)), - onTimeScaleChange: (ts: TimeScale) => { - dispatch( - sqlStatsActions.updateTimeScale({ - ts: ts, - }), - ); - }, - refreshStatementDiagnosticsRequests: () => - dispatch(statementDiagnosticsActions.refresh()), - refreshUserSQLRoles: () => - dispatch(uiConfigActions.refreshUserSQLRoles()), - resetSQLStats: (req: StatementsRequest) => - dispatch(sqlStatsActions.reset(req)), - dismissAlertMessage: () => - dispatch( - localStorageActions.update({ - key: "adminUi/showDiagnosticsModal", - value: false, - }), - ), - onActivateStatementDiagnostics: ( - statementFingerprint: string, - minExecLatency: IDuration, - expiresAfter: IDuration, - ) => { - dispatch( - statementDiagnosticsActions.createReport( - new CreateStatementDiagnosticsReportRequest({ - statement_fingerprint: statementFingerprint, - min_execution_latency: minExecLatency, - expires_after: expiresAfter, + fingerprintsPageProps: { + refreshStatements: (req: StatementsRequest) => + dispatch(sqlStatsActions.refresh(req)), + onTimeScaleChange: (ts: TimeScale) => { + dispatch( + sqlStatsActions.updateTimeScale({ + ts: ts, + }), + ); + }, + refreshStatementDiagnosticsRequests: () => + dispatch(statementDiagnosticsActions.refresh()), + refreshUserSQLRoles: () => + dispatch(uiConfigActions.refreshUserSQLRoles()), + resetSQLStats: (req: StatementsRequest) => + dispatch(sqlStatsActions.reset(req)), + dismissAlertMessage: () => + dispatch( + localStorageActions.update({ + key: "adminUi/showDiagnosticsModal", + value: false, }), ), - ); - dispatch( - analyticsActions.track({ - name: "Statement Diagnostics Clicked", - page: "Statements", - action: "Activated", - }), - ); - }, - onSelectDiagnosticsReportDropdownOption: ( - report: IStatementDiagnosticsReport, - ) => { - if (report.completed) { + onActivateStatementDiagnostics: ( + statementFingerprint: string, + minExecLatency: IDuration, + expiresAfter: IDuration, + ) => { + dispatch( + statementDiagnosticsActions.createReport( + new CreateStatementDiagnosticsReportRequest({ + statement_fingerprint: statementFingerprint, + min_execution_latency: minExecLatency, + expires_after: expiresAfter, + }), + ), + ); dispatch( analyticsActions.track({ name: "Statement Diagnostics Clicked", page: "Statements", - action: "Downloaded", + action: "Activated", }), ); - } else { - dispatch( - statementDiagnosticsActions.cancelReport( - new CancelStatementDiagnosticsReportRequest({ - request_id: report.id, + }, + onSelectDiagnosticsReportDropdownOption: ( + report: IStatementDiagnosticsReport, + ) => { + if (report.completed) { + dispatch( + analyticsActions.track({ + name: "Statement Diagnostics Clicked", + page: "Statements", + action: "Downloaded", }), - ), + ); + } else { + dispatch( + statementDiagnosticsActions.cancelReport( + new CancelStatementDiagnosticsReportRequest({ + request_id: report.id, + }), + ), + ); + dispatch( + analyticsActions.track({ + name: "Statement Diagnostics Clicked", + page: "Statements", + action: "Cancelled", + }), + ); + } + }, + onSearchComplete: (query: string) => { + dispatch( + analyticsActions.track({ + name: "Keyword Searched", + page: "Statements", + }), + ); + dispatch( + localStorageActions.update({ + key: "search/StatementsPage", + value: query, + }), ); + }, + onFilterChange: value => { dispatch( analyticsActions.track({ - name: "Statement Diagnostics Clicked", + name: "Filter Clicked", page: "Statements", - action: "Cancelled", + filterName: "app", + value: value.toString(), }), ); - } - }, - onSearchComplete: (query: string) => { - dispatch( - analyticsActions.track({ - name: "Keyword Searched", - page: "Statements", - }), - ); - dispatch( - localStorageActions.update({ - key: "search/StatementsPage", - value: query, - }), - ); + dispatch( + localStorageActions.update({ + key: "filters/StatementsPage", + value: value, + }), + ); + }, + onSortingChange: ( + tableName: string, + columnName: string, + ascending: boolean, + ) => { + dispatch( + analyticsActions.track({ + name: "Column Sorted", + page: "Statements", + tableName, + columnName, + }), + ); + dispatch( + localStorageActions.update({ + key: "sortSetting/StatementsPage", + value: { columnTitle: columnName, ascending: ascending }, + }), + ); + }, + onStatementClick: () => + dispatch( + analyticsActions.track({ + name: "Statement Clicked", + page: "Statements", + }), + ), + // We use `null` when the value was never set and it will show all columns. + // If the user modifies the selection and no columns are selected, + // the function will save the value as a blank space, otherwise + // it gets saved as `null`. + onColumnsChange: (selectedColumns: string[]) => + dispatch( + localStorageActions.update({ + key: "showColumns/StatementsPage", + value: + selectedColumns.length === 0 ? " " : selectedColumns.join(","), + }), + ), }, - onFilterChange: value => { - dispatch( - analyticsActions.track({ - name: "Filter Clicked", - page: "Statements", - filterName: "app", - value: value.toString(), - }), - ); - dispatch( - localStorageActions.update({ - key: "filters/StatementsPage", - value: value, - }), - ); + activePageProps: mapDispatchToActiveStatementsPageProps(dispatch), + }), + (stateProps, dispatchProps) => ({ + fingerprintsPageProps: { + ...stateProps.fingerprintsPageProps, + ...dispatchProps.fingerprintsPageProps, }, - onSortingChange: ( - tableName: string, - columnName: string, - ascending: boolean, - ) => { - dispatch( - analyticsActions.track({ - name: "Column Sorted", - page: "Statements", - tableName, - columnName, - }), - ); - dispatch( - localStorageActions.update({ - key: "sortSetting/StatementsPage", - value: { columnTitle: columnName, ascending: ascending }, - }), - ); + activePageProps: { + ...stateProps.activePageProps, + ...dispatchProps.activePageProps, }, - onStatementClick: () => - dispatch( - analyticsActions.track({ - name: "Statement Clicked", - page: "Statements", - }), - ), - // We use `null` when the value was never set and it will show all columns. - // If the user modifies the selection and no columns are selected, - // the function will save the value as a blank space, otherwise - // it gets saved as `null`. - onColumnsChange: (selectedColumns: string[]) => - dispatch( - localStorageActions.update({ - key: "showColumns/StatementsPage", - value: - selectedColumns.length === 0 ? " " : selectedColumns.join(","), - }), - ), }), - )(StatementsPage), + )(StatementsPageRoot), ); diff --git a/pkg/ui/workspaces/cluster-ui/src/store/localStorage/localStorage.reducer.ts b/pkg/ui/workspaces/cluster-ui/src/store/localStorage/localStorage.reducer.ts index 28e0ed906c29..0d7159dab5a1 100644 --- a/pkg/ui/workspaces/cluster-ui/src/store/localStorage/localStorage.reducer.ts +++ b/pkg/ui/workspaces/cluster-ui/src/store/localStorage/localStorage.reducer.ts @@ -21,13 +21,19 @@ type SortSetting = { export type LocalStorageState = { "adminUi/showDiagnosticsModal": boolean; + "showColumns/ActiveStatementsPage": string; + "showColumns/ActiveTransactionsPage": string; "showColumns/StatementsPage": string; "showColumns/TransactionPage": string; "showColumns/SessionsPage": string; "timeScale/SQLActivity": TimeScale; + "sortSetting/ActiveStatementsPage": SortSetting; + "sortSetting/ActiveTransactionsPage": SortSetting; "sortSetting/StatementsPage": SortSetting; "sortSetting/TransactionsPage": SortSetting; "sortSetting/SessionsPage": SortSetting; + "filters/ActiveStatementsPage": Filters; + "filters/ActiveTransactionsPage": Filters; "filters/StatementsPage": Filters; "filters/TransactionsPage": Filters; "filters/SessionsPage": Filters; @@ -45,6 +51,15 @@ const defaultSortSetting: SortSetting = { columnTitle: "executionCount", }; +const defaultSortSettingActiveExecutions: SortSetting = { + ascending: false, + columnTitle: "startTime", +}; + +const defaultFiltersActiveExecutions = { + app: defaultFilters.app, +}; + const defaultSessionsSortSetting: SortSetting = { ascending: false, columnTitle: "statementAge", @@ -55,6 +70,12 @@ const initialState: LocalStorageState = { "adminUi/showDiagnosticsModal": Boolean(JSON.parse(localStorage.getItem("adminUi/showDiagnosticsModal"))) || false, + "showColumns/ActiveStatementsPage": + JSON.parse(localStorage.getItem("showColumns/ActiveStatementsPage")) ?? + null, + "showColumns/ActiveTransactionsPage": + JSON.parse(localStorage.getItem("showColumns/ActiveTransactionsPage")) ?? + null, "showColumns/StatementsPage": JSON.parse(localStorage.getItem("showColumns/StatementsPage")) || null, "showColumns/TransactionPage": @@ -64,6 +85,12 @@ const initialState: LocalStorageState = { "timeScale/SQLActivity": JSON.parse(localStorage.getItem("timeScale/SQLActivity")) || defaultTimeScaleSelected, + "sortSetting/ActiveStatementsPage": + JSON.parse(localStorage.getItem("sortSetting/ActiveStatementsPage")) || + defaultSortSettingActiveExecutions, + "sortSetting/ActiveTransactionsPage": + JSON.parse(localStorage.getItem("sortSetting/ActiveTransactionsPage")) || + defaultSortSettingActiveExecutions, "sortSetting/StatementsPage": JSON.parse(localStorage.getItem("sortSetting/StatementsPage")) || defaultSortSetting, @@ -73,6 +100,12 @@ const initialState: LocalStorageState = { "sortSetting/SessionsPage": JSON.parse(localStorage.getItem("sortSetting/SessionsPage")) || defaultSessionsSortSetting, + "filters/ActiveStatementsPage": + JSON.parse(localStorage.getItem("filters/ActiveStatementsPage")) || + defaultFiltersActiveExecutions, + "filters/ActiveTransactionsPage": + JSON.parse(localStorage.getItem("filters/ActiveTransactionsPage")) || + defaultFiltersActiveExecutions, "filters/StatementsPage": JSON.parse(localStorage.getItem("filters/StatementsPage")) || defaultFilters, diff --git a/pkg/ui/workspaces/cluster-ui/src/store/sessions/sessions.reducer.ts b/pkg/ui/workspaces/cluster-ui/src/store/sessions/sessions.reducer.ts index 6e9a00e3f976..ffb84151f9c1 100644 --- a/pkg/ui/workspaces/cluster-ui/src/store/sessions/sessions.reducer.ts +++ b/pkg/ui/workspaces/cluster-ui/src/store/sessions/sessions.reducer.ts @@ -11,17 +11,20 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { cockroach } from "@cockroachlabs/crdb-protobuf-client"; import { DOMAIN_NAME, noopReducer } from "../utils"; +import moment, { Moment } from "moment"; type SessionsResponse = cockroach.server.serverpb.ListSessionsResponse; export type SessionsState = { data: SessionsResponse; + lastUpdated: Moment | null; lastError: Error; valid: boolean; }; const initialState: SessionsState = { data: null, + lastUpdated: null, lastError: null, valid: true, }; @@ -34,6 +37,7 @@ const ssessionsSlice = createSlice({ state.data = action.payload; state.valid = true; state.lastError = null; + state.lastUpdated = moment.utc(); }, failed: (state, action: PayloadAction) => { state.valid = false; diff --git a/pkg/ui/workspaces/cluster-ui/src/store/sessions/sessions.sagas.ts b/pkg/ui/workspaces/cluster-ui/src/store/sessions/sessions.sagas.ts index dc0004a9bf37..0912974529d4 100644 --- a/pkg/ui/workspaces/cluster-ui/src/store/sessions/sessions.sagas.ts +++ b/pkg/ui/workspaces/cluster-ui/src/store/sessions/sessions.sagas.ts @@ -12,7 +12,7 @@ import { all, call, delay, put, takeLatest } from "redux-saga/effects"; import { actions } from "./sessions.reducer"; import { getSessions } from "src/api/sessionsApi"; -import { CACHE_INVALIDATION_PERIOD, throttleWithReset } from "../utils"; +import { throttleWithReset } from "../utils"; import { rootActions } from "../reducers"; export function* refreshSessionsSaga() { @@ -33,9 +33,7 @@ export function* receivedStatementsSaga(delayMs: number) { yield put(actions.invalidated()); } -export function* sessionsSaga( - cacheInvalidationPeriod: number = CACHE_INVALIDATION_PERIOD, -) { +export function* sessionsSaga(cacheInvalidationPeriod: number = 10 * 1000) { yield all([ throttleWithReset( cacheInvalidationPeriod, diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionDetails/activeTransactionDetails.selectors.tsx b/pkg/ui/workspaces/cluster-ui/src/transactionDetails/activeTransactionDetails.selectors.tsx new file mode 100644 index 000000000000..1a8f899fee1d --- /dev/null +++ b/pkg/ui/workspaces/cluster-ui/src/transactionDetails/activeTransactionDetails.selectors.tsx @@ -0,0 +1,30 @@ +// 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 { createSelector } from "reselect"; +import { AppState } from "src"; +import { match, RouteComponentProps } from "react-router-dom"; +import { getMatchParamByName } from "src/util/query"; +import { executionIdAttr } from "../util/constants"; +import { getActiveTransactionsFromSessions } from "../activeExecutions/activeStatementUtils"; + +export const selectActiveTransaction = createSelector( + (_: AppState, props: RouteComponentProps) => props.match, + (state: AppState) => state.adminUI.sessions, + (match: match, response) => { + if (!response.data) return null; + + const executionID = getMatchParamByName(match, executionIdAttr); + return getActiveTransactionsFromSessions( + response.data, + response.lastUpdated, + ).find(stmt => stmt.executionID === executionID); + }, +); diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionDetails/activeTransactionDetailsConnected.tsx b/pkg/ui/workspaces/cluster-ui/src/transactionDetails/activeTransactionDetailsConnected.tsx new file mode 100644 index 000000000000..228206916cd1 --- /dev/null +++ b/pkg/ui/workspaces/cluster-ui/src/transactionDetails/activeTransactionDetailsConnected.tsx @@ -0,0 +1,43 @@ +// 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 { RouteComponentProps, withRouter } from "react-router-dom"; +import { connect } from "react-redux"; +import { Dispatch } from "redux"; +import { actions as sessionsActions } from "src/store/sessions"; +import { AppState } from "../store"; +import { + ActiveTransactionDetails, + ActiveTransactionDetailsDispatchProps, +} from "./activeTransactionDetails"; +import { selectActiveTransaction } from "./activeTransactionDetails.selectors"; +import { ActiveTransactionDetailsStateProps } from "."; + +// For tenant cases, we don't show information about node, regions and +// diagnostics. +const mapStateToProps = ( + state: AppState, + props: RouteComponentProps, +): ActiveTransactionDetailsStateProps => { + return { + transaction: selectActiveTransaction(state, props), + match: props.match, + }; +}; + +const mapDispatchToProps = ( + dispatch: Dispatch, +): ActiveTransactionDetailsDispatchProps => ({ + refreshSessions: () => dispatch(sessionsActions.refresh()), +}); + +export const ActiveTransactionDetailsPageConnected = withRouter( + connect(mapStateToProps, mapDispatchToProps)(ActiveTransactionDetails), +); diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionDetails/index.ts b/pkg/ui/workspaces/cluster-ui/src/transactionDetails/index.ts index 38913885069b..7487c1a8de91 100644 --- a/pkg/ui/workspaces/cluster-ui/src/transactionDetails/index.ts +++ b/pkg/ui/workspaces/cluster-ui/src/transactionDetails/index.ts @@ -11,3 +11,4 @@ export * from "./transactionDetails"; export * from "./transactionDetailsConnected"; export * from "./activeTransactionDetails"; +export * from "./activeTransactionDetailsConnected"; diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/activeTransactionsPage.selectors.tsx b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/activeTransactionsPage.selectors.tsx new file mode 100644 index 000000000000..cf99ae86ce41 --- /dev/null +++ b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/activeTransactionsPage.selectors.tsx @@ -0,0 +1,92 @@ +// 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 { createSelector } from "reselect"; +import { getActiveTransactionsFromSessions } from "../activeExecutions/activeStatementUtils"; +import { localStorageSelector } from "src/statementsPage/statementsPage.selectors"; +import { + ActiveTransactionFilters, + ActiveTransactionsViewDispatchProps, + ActiveTransactionsViewStateProps, + AppState, + SortSetting, +} from "src"; +import { actions as sessionsActions } from "src/store/sessions"; +import { actions as localStorageActions } from "src/store/localStorage"; +import { Dispatch } from "redux"; + +export const selectActiveTransactions = createSelector( + (state: AppState) => state.adminUI.sessions, + response => { + if (!response.data) return []; + + return getActiveTransactionsFromSessions( + response.data, + response.lastUpdated, + ); + }, +); + +export const selectSortSetting = (state: AppState): SortSetting => + localStorageSelector(state)["sortSetting/ActiveTransactionsPage"]; + +export const selectFilters = (state: AppState): ActiveTransactionFilters => + localStorageSelector(state)["filters/ActiveTransactionsPage"]; + +const selectLocalStorageColumns = (state: AppState) => { + const localStorage = localStorageSelector(state); + return localStorage["showColumns/ActiveTransactionsPage"]; +}; + +export const selectColumns = createSelector( + selectLocalStorageColumns, + value => { + if (value == null) return null; + + return value.split(",").map(col => col.trim()); + }, +); + +export const mapStateToActiveTransactionsPageProps = ( + state: AppState, +): ActiveTransactionsViewStateProps => ({ + transactions: selectActiveTransactions(state), + sessionsError: state.adminUI.sessions.lastError, + selectedColumns: selectColumns(state), + sortSetting: selectSortSetting(state), + filters: selectFilters(state), +}); + +export const mapDispatchToActiveTransactionsPageProps = ( + dispatch: Dispatch, +): ActiveTransactionsViewDispatchProps => ({ + refreshSessions: () => dispatch(sessionsActions.refresh()), + onColumnsSelect: columns => + dispatch( + localStorageActions.update({ + key: "showColumns/ActiveTransactionsPage", + value: columns ? columns.join(", ") : " ", + }), + ), + onFiltersChange: (filters: ActiveTransactionFilters) => + dispatch( + localStorageActions.update({ + key: "filters/ActiveTransactionsPage", + value: filters, + }), + ), + onSortChange: (ss: SortSetting) => + dispatch( + localStorageActions.update({ + key: "sortSetting/ActiveTransactionsPage", + value: ss, + }), + ), +}); diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPageConnected.tsx b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPageConnected.tsx index 2c7a7aa56c11..eba0bd180659 100644 --- a/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPageConnected.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/transactionsPage/transactionsPageConnected.tsx @@ -14,7 +14,6 @@ import { Dispatch } from "redux"; import { AppState } from "src/store"; import { actions as sqlStatsActions } from "src/store/sqlStats"; -import { TransactionsPage } from "./transactionsPage"; import { TransactionsPageStateProps, TransactionsPageDispatchProps, @@ -35,90 +34,130 @@ import { actions as localStorageActions } from "../store/localStorage"; import { Filters } from "../queryFilter"; import { actions as analyticsActions } from "../store/analytics"; import { TimeScale } from "../timeScaleDropdown"; +import { + TransactionsPageRoot, + TransactionsPageRootProps, +} from "./transactionsPageRoot"; +import { + mapStateToActiveTransactionsPageProps, + mapDispatchToActiveTransactionsPageProps, +} from "./activeTransactionsPage.selectors"; +import { + ActiveTransactionsViewStateProps, + ActiveTransactionsViewDispatchProps, +} from "./activeTransactionsView"; + +type StateProps = { + fingerprintsPageProps: TransactionsPageStateProps & RouteComponentProps; + activePageProps: ActiveTransactionsViewStateProps; +}; + +type DispatchProps = { + fingerprintsPageProps: TransactionsPageDispatchProps; + activePageProps: ActiveTransactionsViewDispatchProps; +}; export const TransactionsPageConnected = withRouter( connect< - TransactionsPageStateProps, - TransactionsPageDispatchProps, - RouteComponentProps + StateProps, + DispatchProps, + RouteComponentProps, + TransactionsPageRootProps >( - (state: AppState) => ({ - columns: selectTxnColumns(state), - data: selectTransactionsData(state), - timeScale: selectTimeScale(state), - error: selectTransactionsLastError(state), - filters: selectFilters(state), - isTenant: selectIsTenant(state), - nodeRegions: nodeRegionsByIDSelector(state), - search: selectSearch(state), - sortSetting: selectSortSetting(state), + (state: AppState, props) => ({ + fingerprintsPageProps: { + ...props, + columns: selectTxnColumns(state), + data: selectTransactionsData(state), + timeScale: selectTimeScale(state), + error: selectTransactionsLastError(state), + filters: selectFilters(state), + isTenant: selectIsTenant(state), + nodeRegions: nodeRegionsByIDSelector(state), + search: selectSearch(state), + sortSetting: selectSortSetting(state), + }, + activePageProps: mapStateToActiveTransactionsPageProps(state), }), (dispatch: Dispatch) => ({ - refreshData: (req: StatementsRequest) => - dispatch(sqlStatsActions.refresh(req)), - resetSQLStats: (req: StatementsRequest) => - dispatch(sqlStatsActions.reset(req)), - onTimeScaleChange: (ts: TimeScale) => { - dispatch( - sqlStatsActions.updateTimeScale({ - ts: ts, - }), - ); + fingerprintsPageProps: { + refreshData: (req: StatementsRequest) => + dispatch(sqlStatsActions.refresh(req)), + resetSQLStats: (req: StatementsRequest) => + dispatch(sqlStatsActions.reset(req)), + onTimeScaleChange: (ts: TimeScale) => { + dispatch( + sqlStatsActions.updateTimeScale({ + ts: ts, + }), + ); + }, + // We use `null` when the value was never set and it will show all columns. + // If the user modifies the selection and no columns are selected, + // the function will save the value as a blank space, otherwise + // it gets saved as `null`. + onColumnsChange: (selectedColumns: string[]) => + dispatch( + localStorageActions.update({ + key: "showColumns/TransactionPage", + value: + selectedColumns.length === 0 ? " " : selectedColumns.join(","), + }), + ), + onSortingChange: ( + tableName: string, + columnName: string, + ascending: boolean, + ) => { + dispatch( + localStorageActions.update({ + key: "sortSetting/TransactionsPage", + value: { columnTitle: columnName, ascending: ascending }, + }), + ); + }, + onFilterChange: (value: Filters) => { + dispatch( + analyticsActions.track({ + name: "Filter Clicked", + page: "Transactions", + filterName: "app", + value: value.toString(), + }), + ); + dispatch( + localStorageActions.update({ + key: "filters/TransactionsPage", + value: value, + }), + ); + }, + onSearchComplete: (query: string) => { + dispatch( + analyticsActions.track({ + name: "Keyword Searched", + page: "Transactions", + }), + ); + dispatch( + localStorageActions.update({ + key: "search/TransactionsPage", + value: query, + }), + ); + }, }, - // We use `null` when the value was never set and it will show all columns. - // If the user modifies the selection and no columns are selected, - // the function will save the value as a blank space, otherwise - // it gets saved as `null`. - onColumnsChange: (selectedColumns: string[]) => - dispatch( - localStorageActions.update({ - key: "showColumns/TransactionPage", - value: - selectedColumns.length === 0 ? " " : selectedColumns.join(","), - }), - ), - onSortingChange: ( - tableName: string, - columnName: string, - ascending: boolean, - ) => { - dispatch( - localStorageActions.update({ - key: "sortSetting/TransactionsPage", - value: { columnTitle: columnName, ascending: ascending }, - }), - ); - }, - onFilterChange: (value: Filters) => { - dispatch( - analyticsActions.track({ - name: "Filter Clicked", - page: "Transactions", - filterName: "app", - value: value.toString(), - }), - ); - dispatch( - localStorageActions.update({ - key: "filters/TransactionsPage", - value: value, - }), - ); + activePageProps: mapDispatchToActiveTransactionsPageProps(dispatch), + }), + (stateProps, dispatchProps) => ({ + fingerprintsPageProps: { + ...stateProps.fingerprintsPageProps, + ...dispatchProps.fingerprintsPageProps, }, - onSearchComplete: (query: string) => { - dispatch( - analyticsActions.track({ - name: "Keyword Searched", - page: "Transactions", - }), - ); - dispatch( - localStorageActions.update({ - key: "search/TransactionsPage", - value: query, - }), - ); + activePageProps: { + ...stateProps.activePageProps, + ...dispatchProps.activePageProps, }, }), - )(TransactionsPage), + )(TransactionsPageRoot), ); From b9969597843e35399295b58b6cc1694e761930f7 Mon Sep 17 00:00:00 2001 From: Rebecca Taft Date: Mon, 28 Mar 2022 09:33:49 -0500 Subject: [PATCH 2/4] opt: disable index recommendations with PARTITION ALL BY This commit disables index recommendations for tables with PARTITION ALL BY (including REGIONAL BY ROW tables) because the current recommendations are not valid. This is a backportable fix, but a future PR will fix these recommendations and reenable them. Release note (sql change): Disabled index recommendations in EXPLAIN output for REGIONAL BY ROW tables, as the previous recommendations were not valid. --- .../logic_test/regional_by_row_query_behavior | 53 +++++++++++++++++++ pkg/sql/opt/cat/table.go | 4 ++ pkg/sql/opt/indexrec/index_candidate_set.go | 7 +++ pkg/sql/opt/testutils/testcat/test_catalog.go | 5 ++ pkg/sql/opt_catalog.go | 10 ++++ 5 files changed, 79 insertions(+) diff --git a/pkg/ccl/logictestccl/testdata/logic_test/regional_by_row_query_behavior b/pkg/ccl/logictestccl/testdata/logic_test/regional_by_row_query_behavior index 6f2ad029ba86..25c94c6239e4 100644 --- a/pkg/ccl/logictestccl/testdata/logic_test/regional_by_row_query_behavior +++ b/pkg/ccl/logictestccl/testdata/logic_test/regional_by_row_query_behavior @@ -2809,3 +2809,56 @@ SELECT * FROM [EXPLAIN SELECT * FROM regional_by_row_table_as1 LIMIT 3] OFFSET 2 table: regional_by_row_table_as1@regional_by_row_table_as1_pkey spans: [/'ca-central-1' - /'us-east-1'] limit: 3 + +subtest index_recommendations + +# Enable vectorize so we get consistent EXPLAIN output. We cannot use the +# OFFSET 2 strategy for these tests because that disables the index +# recommendation (index recommendations are only used when EXPLAIN is the +# root of the query tree). +statement ok +SET index_recommendations_enabled = true; +SET vectorize=on + +statement ok +CREATE TABLE users ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name STRING NOT NULL, + email STRING NOT NULL UNIQUE, + INDEX (name) +) LOCALITY REGIONAL BY ROW + +# Check that we don't recommend indexes that already exist. +query T +EXPLAIN INSERT INTO users (name, email) +VALUES ('Craig Roacher', 'craig@cockroachlabs.com') +---- +distribution: local +vectorized: true +· +• root +│ +├── • insert +│ │ into: users(id, name, email, crdb_region) +│ │ +│ └── • buffer +│ │ label: buffer 1 +│ │ +│ └── • values +│ size: 5 columns, 1 row +│ +└── • constraint-check + │ + └── • error if rows + │ + └── • lookup join (semi) + │ table: users@users_email_key + │ lookup condition: (column2 = email) AND (crdb_region IN ('ap-southeast-2', 'ca-central-1', 'us-east-1')) + │ pred: (id_default != id) OR (crdb_region_default != crdb_region) + │ + └── • scan buffer + label: buffer 1 + +statement ok +SET index_recommendations_enabled = false; +RESET vectorize diff --git a/pkg/sql/opt/cat/table.go b/pkg/sql/opt/cat/table.go index 955070ef4b7e..fd91ec2aa11d 100644 --- a/pkg/sql/opt/cat/table.go +++ b/pkg/sql/opt/cat/table.go @@ -136,6 +136,10 @@ type Table interface { // Zone returns a table's zone. Zone() Zone + + // IsPartitionAllBy returns true if this is a PARTITION ALL BY table. This + // includes REGIONAL BY ROW tables. + IsPartitionAllBy() bool } // CheckConstraint contains the SQL text and the validity status for a check diff --git a/pkg/sql/opt/indexrec/index_candidate_set.go b/pkg/sql/opt/indexrec/index_candidate_set.go index 0e0317aa5a4f..10e266e8461a 100644 --- a/pkg/sql/opt/indexrec/index_candidate_set.go +++ b/pkg/sql/opt/indexrec/index_candidate_set.go @@ -373,7 +373,14 @@ func addIndexToCandidates( return } + // Do not add indexes to PARTITION ALL BY tables. + // TODO(rytaft): Support these tables by adding implicit partitioning columns. + if currTable.IsPartitionAllBy() { + return + } + // Do not add indexes on spatial columns. + // TODO(rytaft): Support spatial predicates like st_contains() etc. for _, indexCol := range newIndex { colFamily := indexCol.Column.DatumType().Family() if colFamily == types.GeometryFamily || colFamily == types.GeographyFamily { diff --git a/pkg/sql/opt/testutils/testcat/test_catalog.go b/pkg/sql/opt/testutils/testcat/test_catalog.go index c8800b7e15ab..75fff204eab0 100644 --- a/pkg/sql/opt/testutils/testcat/test_catalog.go +++ b/pkg/sql/opt/testutils/testcat/test_catalog.go @@ -755,6 +755,11 @@ func (tt *Table) Zone() cat.Zone { return cat.AsZone(&zone) } +// IsPartitionAllBy is part of the cat.Table interface. +func (tt *Table) IsPartitionAllBy() bool { + return false +} + // FindOrdinal returns the ordinal of the column with the given name. func (tt *Table) FindOrdinal(name string) int { for i, col := range tt.Columns { diff --git a/pkg/sql/opt_catalog.go b/pkg/sql/opt_catalog.go index b508475727b5..c2a61c070898 100644 --- a/pkg/sql/opt_catalog.go +++ b/pkg/sql/opt_catalog.go @@ -1158,6 +1158,11 @@ func (ot *optTable) Zone() cat.Zone { return ot.zone } +// IsPartitionAllBy is part of the cat.Table interface. +func (ot *optTable) IsPartitionAllBy() bool { + return ot.desc.IsPartitionAllBy() +} + // lookupColumnOrdinal returns the ordinal of the column with the given ID. A // cache makes the lookup O(1). func (ot *optTable) lookupColumnOrdinal(colID descpb.ColumnID) (int, error) { @@ -2073,6 +2078,11 @@ func (ot *optVirtualTable) Zone() cat.Zone { panic(errors.AssertionFailedf("no zone")) } +// IsPartitionAllBy is part of the cat.Table interface. +func (ot *optVirtualTable) IsPartitionAllBy() bool { + return false +} + // CollectTypes is part of the cat.DataSource interface. func (ot *optVirtualTable) CollectTypes(ord int) (descpb.IDs, error) { col := ot.desc.AllColumns()[ord] From 51152ff9467d0934f577cd591bf33d051935bfde Mon Sep 17 00:00:00 2001 From: Nick Travers Date: Tue, 22 Mar 2022 14:25:14 -0700 Subject: [PATCH 3/4] roachtest: sstable-corruption: collect manifests; additional logging Add additional logging of the contents of the data directory and collect the Pebble manifests when the test fails to find tables to corrupt. This will aid in diagnosing the test failures. Touches #78121. Release note: None. --- pkg/cmd/roachtest/tests/sstable_corruption.go | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/pkg/cmd/roachtest/tests/sstable_corruption.go b/pkg/cmd/roachtest/tests/sstable_corruption.go index bfce99241474..e19decca3eb8 100644 --- a/pkg/cmd/roachtest/tests/sstable_corruption.go +++ b/pkg/cmd/roachtest/tests/sstable_corruption.go @@ -13,6 +13,7 @@ package tests import ( "context" "fmt" + "path/filepath" "strconv" "strings" "time" @@ -53,15 +54,19 @@ func runSSTableCorruption(ctx context.Context, t test.Test, c cluster.Cluster) { opts.RoachprodOpts.Wait = true c.Stop(ctx, t.L(), opts, crdbNodes) - const findTablesCmd = "" + + const nTables = 6 + var dumpManifestCmd = "" + // Take the latest manifest file ... "ls -tr {store-dir}/MANIFEST-* | tail -n1 | " + // ... dump its contents ... "xargs ./cockroach debug pebble manifest dump | " + - // ... shuffle the files to distribute corruption over the LSM. + // ... filter for SSTables that contain table data. + "grep -v added | grep -v deleted | grep '/Table/'" + var findTablesCmd = dumpManifestCmd + "| " + + // Shuffle the files to distribute corruption over the LSM ... "shuf | " + - // ... filter for up to six SSTables that contain table data. - "grep -v added | grep -v deleted | grep '/Table/' | tail -n6" + // ... take a fixed number of tables. + fmt.Sprintf("tail -n %d", nTables) for _, node := range corruptNodes { result, err := c.RunWithDetailsSingleNode(ctx, t.L(), c.Node(node), findTablesCmd) @@ -69,15 +74,45 @@ func runSSTableCorruption(ctx context.Context, t test.Test, c cluster.Cluster) { t.Fatalf("could not find tables to corrupt: %s\nstdout: %s\nstderr: %s", err, result.Stdout, result.Stderr) } tableSSTs := strings.Split(strings.TrimSpace(result.Stdout), "\n") - if len(tableSSTs) == 0 { - t.Fatal("expected at least one sst containing table keys only, got none") + if len(tableSSTs) != nTables { + // We couldn't find enough tables to corrupt. As there should be an + // abundance of tables, this warrants further investigation. To aid in + // such an investigation, print the contents of the data directory. + cmd := "ls -l {store-dir}" + result, err = c.RunWithDetailsSingleNode(ctx, t.L(), c.Node(node), cmd) + if err == nil { + t.Status("store dir contents:\n", result.Stdout) + } + // Fetch the MANIFEST files from this node. + result, err = c.RunWithDetailsSingleNode( + ctx, t.L(), c.Node(node), + "tar czf {store-dir}/manifests.tar.gz {store-dir}/MANIFEST-*", + ) + if err != nil { + t.Fatalf("could not create manifest file archive: %s", err) + } + result, err = c.RunWithDetailsSingleNode(ctx, t.L(), c.Node(node), "echo", "-n", "{store-dir}") + if err != nil { + t.Fatalf("could not infer store directory: %s", err) + } + storeDirectory := result.Stdout + srcPath := filepath.Join(storeDirectory, "manifests.tar.gz") + dstPath := filepath.Join(t.ArtifactsDir(), fmt.Sprintf("manifests.%d.tar.gz", node)) + err = c.Get(ctx, t.L(), srcPath, dstPath, c.Node(node)) + if err != nil { + t.Fatalf("could not fetch manifest archive: %s", err) + } + t.Fatalf( + "expected %d SSTables containing table keys, got %d: %s", + nTables, len(tableSSTs), tableSSTs, + ) } // Corrupt the SSTs. for _, sstLine := range tableSSTs { sstLine = strings.TrimSpace(sstLine) firstFileIdx := strings.Index(sstLine, ":") if firstFileIdx < 0 { - t.Fatalf("unexpected format for sst line: %s", sstLine) + t.Fatalf("unexpected format for sst line: %q", sstLine) } _, err = strconv.Atoi(sstLine[:firstFileIdx]) if err != nil { From 6a99140243bda6517f08676534d04aa68753d9f4 Mon Sep 17 00:00:00 2001 From: Rail Aliiev Date: Mon, 28 Mar 2022 14:47:36 -0400 Subject: [PATCH 4/4] roachprod: do not send status report to slack Previously, the roachprod GC job posted cluster status on each run. This made the #roachprod-status channel very spammy and not actionable. This patch removes the status generation part. Release note: None --- pkg/roachprod/cloud/gc.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pkg/roachprod/cloud/gc.go b/pkg/roachprod/cloud/gc.go index 1c8a5c6a324f..a469e05deb8a 100644 --- a/pkg/roachprod/cloud/gc.go +++ b/pkg/roachprod/cloud/gc.go @@ -312,11 +312,7 @@ func GCClusters(l *logger.Logger, cloud *Cloud, dryrun bool) error { } } - // Send out notification to #roachprod-status. client := makeSlackClient() - channel, _ := findChannel(client, "roachprod-status", "") - postStatus(l, client, channel, dryrun, &s, badVMs) - // Send out user notifications if any of the user's clusters are expired or // will be destroyed. for user, status := range users { @@ -330,6 +326,7 @@ func GCClusters(l *logger.Logger, cloud *Cloud, dryrun bool) error { } } + channel, _ := findChannel(client, "roachprod-status", "") if !dryrun { if len(badVMs) > 0 { // Destroy bad VMs.