github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/views/reports/containers/range/index.tsx (about) 1 // Copyright 2018 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 _ from "lodash"; 12 import Long from "long"; 13 import React from "react"; 14 import { Helmet } from "react-helmet"; 15 import { connect } from "react-redux"; 16 import { RouteComponentProps, withRouter } from "react-router-dom"; 17 18 import * as protos from "src/js/protos"; 19 import { 20 allocatorRangeRequestKey, 21 rangeRequestKey, 22 rangeLogRequestKey, 23 refreshAllocatorRange, 24 refreshRange, 25 refreshRangeLog, 26 } from "src/redux/apiReducers"; 27 import { CachedDataReducerState } from "src/redux/cachedDataReducer"; 28 import { AdminUIState } from "src/redux/state"; 29 import { rangeIDAttr } from "src/util/constants"; 30 import { FixLong } from "src/util/fixLong"; 31 import ConnectionsTable from "src/views/reports/containers/range/connectionsTable"; 32 import RangeTable from "src/views/reports/containers/range/rangeTable"; 33 import LogTable from "src/views/reports/containers/range/logTable"; 34 import AllocatorOutput from "src/views/reports/containers/range/allocator"; 35 import RangeInfo from "src/views/reports/containers/range/rangeInfo"; 36 import LeaseTable from "src/views/reports/containers/range/leaseTable"; 37 import { getMatchParamByName } from "src/util/query"; 38 39 interface RangeOwnProps { 40 range: CachedDataReducerState<protos.cockroach.server.serverpb.RangeResponse>; 41 allocator: CachedDataReducerState<protos.cockroach.server.serverpb.AllocatorRangeResponse>; 42 rangeLog: CachedDataReducerState<protos.cockroach.server.serverpb.RangeLogResponse>; 43 refreshRange: typeof refreshRange; 44 refreshAllocatorRange: typeof refreshAllocatorRange; 45 refreshRangeLog: typeof refreshRangeLog; 46 } 47 48 type RangeProps = RangeOwnProps & RouteComponentProps; 49 50 function ErrorPage(props: { 51 rangeID: string; 52 errorText: string; 53 range?: CachedDataReducerState<protos.cockroach.server.serverpb.RangeResponse>; 54 }) { 55 return ( 56 <div className="section"> 57 <h1 className="base-heading">Range Report for r{props.rangeID}</h1> 58 <h2 className="base-heading">{props.errorText}</h2> 59 <ConnectionsTable range={props.range} /> 60 </div> 61 ); 62 } 63 64 function rangeRequestFromProps(props: RangeProps) { 65 const rangeId = getMatchParamByName(props.match, rangeIDAttr); 66 return new protos.cockroach.server.serverpb.RangeRequest({ 67 range_id: Long.fromString(rangeId), 68 }); 69 } 70 71 function allocatorRequestFromProps(props: RangeProps) { 72 const rangeId = getMatchParamByName(props.match, rangeIDAttr); 73 return new protos.cockroach.server.serverpb.AllocatorRangeRequest({ 74 range_id: Long.fromString(rangeId), 75 }); 76 } 77 78 function rangeLogRequestFromProps(props: RangeProps) { 79 const rangeId = getMatchParamByName(props.match, rangeIDAttr); 80 // TODO(bram): Remove this limit once #18159 is resolved. 81 return new protos.cockroach.server.serverpb.RangeLogRequest({ 82 range_id: Long.fromString(rangeId), 83 limit: -1, 84 }); 85 } 86 87 /** 88 * Renders the Range Report page. 89 */ 90 export class Range extends React.Component<RangeProps, {}> { 91 refresh(props = this.props) { 92 props.refreshRange(rangeRequestFromProps(props)); 93 props.refreshAllocatorRange(allocatorRequestFromProps(props)); 94 props.refreshRangeLog(rangeLogRequestFromProps(props)); 95 } 96 97 componentDidMount() { 98 // Refresh nodes status query when mounting. 99 this.refresh(); 100 } 101 102 componentDidUpdate(prevProps: RangeProps) { 103 if (!_.isEqual(this.props.location, prevProps.location)) { 104 this.refresh(this.props); 105 } 106 } 107 108 render() { 109 const { range, match } = this.props; 110 const rangeID = getMatchParamByName(match, rangeIDAttr); 111 112 // A bunch of quick error cases. 113 if (!_.isNil(range) && !_.isNil(range.lastError)) { 114 return ( 115 <ErrorPage 116 rangeID={rangeID} 117 errorText={`Error loading range ${range.lastError}`} 118 /> 119 ); 120 } 121 if (_.isNil(range) || _.isEmpty(range.data)) { 122 return ( 123 <ErrorPage rangeID={rangeID} errorText={`Loading cluster status...`} /> 124 ); 125 } 126 const responseRangeID = FixLong(range.data.range_id); 127 if (!responseRangeID.eq(rangeID)) { 128 return ( 129 <ErrorPage rangeID={rangeID} errorText={`Updating cluster status...`} /> 130 ); 131 } 132 if (responseRangeID.isNegative() || responseRangeID.isZero()) { 133 return ( 134 <ErrorPage 135 rangeID={rangeID} 136 errorText={`Range ID must be a positive non-zero integer. "${rangeID}"`} 137 /> 138 ); 139 } 140 141 // Did we get any responses? 142 if (!_.some(range.data.responses_by_node_id, resp => resp.infos.length > 0)) { 143 return ( 144 <ErrorPage 145 rangeID={rangeID} 146 errorText={`No results found, perhaps r${rangeID} doesn't exist.`} 147 range={range} 148 /> 149 ); 150 } 151 152 // Collect all the infos and sort them, putting the leader (or the replica 153 // with the highest term, first. 154 const infos = _.orderBy( 155 _.flatMap(range.data.responses_by_node_id, resp => { 156 if (resp.response && _.isEmpty(resp.error_message)) { 157 return resp.infos; 158 } 159 return []; 160 }), 161 [ 162 info => RangeInfo.IsLeader(info), 163 info => FixLong(info.raft_state.applied).toNumber(), 164 info => FixLong(info.raft_state.hard_state.term).toNumber(), 165 info => { 166 const localReplica = RangeInfo.GetLocalReplica(info); 167 return _.isNil(localReplica) ? 0 : localReplica.replica_id; 168 }, 169 ], 170 ["desc", "desc", "desc", "asc"], 171 ); 172 173 // Gather all replica IDs. 174 const replicas = _.chain(infos) 175 .flatMap(info => info.state.state.desc.internal_replicas) 176 .sortBy(rep => rep.replica_id) 177 .sortedUniqBy(rep => rep.replica_id) 178 .value(); 179 180 return ( 181 <div className="section"> 182 <Helmet title={ `r${responseRangeID.toString()} Range | Debug` } /> 183 <h1 className="base-heading">Range Report for r{responseRangeID.toString()}</h1> 184 <RangeTable infos={infos} replicas={replicas} /> 185 <LeaseTable info={_.head(infos)} /> 186 <ConnectionsTable range={range} /> 187 <AllocatorOutput allocator={this.props.allocator} /> 188 <LogTable rangeID={responseRangeID} log={this.props.rangeLog} /> 189 </div> 190 ); 191 } 192 } 193 const mapStateToProps = (state: AdminUIState, props: RangeProps) => ({ 194 range: state.cachedData.range[rangeRequestKey(rangeRequestFromProps(props))], 195 allocator: state.cachedData.allocatorRange[allocatorRangeRequestKey(allocatorRequestFromProps(props))], 196 rangeLog: state.cachedData.rangeLog[rangeLogRequestKey(rangeLogRequestFromProps(props))], 197 }); 198 199 const mapDispatchToProps = { 200 refreshRange, 201 refreshAllocatorRange, 202 refreshRangeLog, 203 }; 204 205 export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Range));