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));