import { OctopusArms, baseHeight } from './util/OctopusArms.jsx'; import { displayName, metricToFormatter } from './util/Utils.js'; import Card from '@material-ui/core/Card'; import CardContent from '@material-ui/core/CardContent'; import Grid from '@material-ui/core/Grid'; import PropTypes from 'prop-types'; import React from 'react'; import { StyledProgress } from './util/Progress.jsx'; import Table from '@material-ui/core/Table'; import TableBody from '@material-ui/core/TableBody'; import TableCell from '@material-ui/core/TableCell'; import TableRow from '@material-ui/core/TableRow'; import Typography from '@material-ui/core/Typography'; import _ from 'lodash'; import { getSuccessRateClassification } from './util/MetricUtils.jsx' ; const maxNumNeighbors = 6; // max number of neighbor nodes to show in the octopus graph export default class Octopus extends React.Component { static defaultProps = { neighbors: {}, resource: {}, unmeshedSources: [] } static propTypes = { neighbors: PropTypes.shape({}), resource: PropTypes.shape({}), unmeshedSources: PropTypes.arrayOf(PropTypes.shape({})), } getNeighborDisplayData = neighbors => { // only display maxNumNeighbors neighboring nodes in the octopus graph, // otherwise it will be really tall let upstreams = _.sortBy(neighbors.upstream, "resource.successRate"); let downstreams = _.sortBy(neighbors.downstream, "resource.successRate"); let display = { upstreams: { displayed: upstreams, collapsed: [] }, downstreams: { displayed: downstreams, collapsed: [] } }; if (_.size(upstreams) > maxNumNeighbors) { display.upstreams.displayed = _.take(upstreams, maxNumNeighbors); display.upstreams.collapsed = _.slice(upstreams, maxNumNeighbors, _.size(upstreams)); } if (_.size(downstreams) > maxNumNeighbors) { display.downstreams.displayed = _.take(downstreams, maxNumNeighbors); display.downstreams.collapsed = _.slice(downstreams, maxNumNeighbors, _.size(downstreams)); } return display; } linkedResourceTitle = (resource, display) => { return _.isNil(resource.namespace) ? display : ; } renderResourceCard(resource, type) { let display = displayName(resource); let classification = getSuccessRateClassification(resource.successRate); let Progress = StyledProgress(classification); return ( { this.linkedResourceTitle(resource, display) } SR {metricToFormatter["SUCCESS_RATE"](resource.successRate)} RPS {metricToFormatter["NO_UNIT"](resource.requestRate)} P99 {metricToFormatter["LATENCY"](_.get(resource, "latency.P99"))}
); } renderUnmeshedResources = unmeshedResources => { return ( Unmeshed { _.map(unmeshedResources, r => { let display = displayName(r); return {display}; }) } ); } renderCollapsedNeighbors = neighbors => { return ( { _.map(neighbors, r => { let display = displayName(r); return {this.linkedResourceTitle(r, display)}; }) } ); } renderArrowCol = (numNeighbors, isOutbound) => { let width = 80; let showArrow = numNeighbors > 0; let isEven = numNeighbors % 2 === 0; let middleElementIndex = isEven ? ((numNeighbors - 1) / 2) : _.floor(numNeighbors / 2); let arrowTypes = _.map(_.times(numNeighbors), i => { if (i < middleElementIndex) { let height = (_.ceil(middleElementIndex - i) - 1) * baseHeight + (baseHeight / 2); return { type: "up", inboundType: "down", height }; } else if (i === middleElementIndex) { return { type: "flat", inboundType: "flat", height: baseHeight }; } else { let height = (_.ceil(i - middleElementIndex) - 1) * baseHeight + (baseHeight / 2); return { type: "down", inboundType: "up", height }; } }); let height = numNeighbors * baseHeight; let svg = ( { _.map(arrowTypes, arrow => { let arrowType = isOutbound ? arrow.type : arrow.inboundType; return OctopusArms[arrowType](width, height, arrow.height, isOutbound, isEven); }) } ); return !showArrow ? null : svg; } render() { let { resource, neighbors, unmeshedSources } = this.props; if (_.isEmpty(resource)) { return null; } let display = this.getNeighborDisplayData(neighbors); let numUpstreams = _.size(display.upstreams.displayed) + (_.isEmpty(unmeshedSources) ? 0 : 1) + (_.isEmpty(display.upstreams.collapsed) ? 0 : 1); let numDownstreams = _.size(display.downstreams.displayed) + (_.isEmpty(display.downstreams.collapsed) ? 0 : 1); return (
{_.map(display.upstreams.displayed, n => this.renderResourceCard(n, "neighbor"))} {_.isEmpty(unmeshedSources) ? null : this.renderUnmeshedResources(unmeshedSources)} {_.isEmpty(display.upstreams.collapsed) ? null : this.renderCollapsedNeighbors(display.upstreams.collapsed)} {this.renderArrowCol(numUpstreams, false)} {this.renderResourceCard(resource, "main")} {this.renderArrowCol(numDownstreams, true)} {_.map(display.downstreams.displayed, n => this.renderResourceCard(n, "neighbor"))} {_.isEmpty(display.downstreams.collapsed) ? null : this.renderCollapsedNeighbors(display.downstreams.collapsed)}
); } }