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 _ceil from 'lodash/ceil'; import _floor from 'lodash/floor'; import _get from 'lodash/get'; import _isEmpty from 'lodash/isEmpty'; import _isNil from 'lodash/isNil'; import _orderBy from 'lodash/orderBy'; import _size from 'lodash/size'; import _slice from 'lodash/slice'; import _take from 'lodash/take'; import _times from 'lodash/times'; import { getSuccessRateClassification } from './util/MetricUtils.jsx' ; import { withStyles } from "@material-ui/core/styles"; const maxNumNeighbors = 6; // max number of neighbor nodes to show in the octopus graph const styles = () => ({ graphContainer: { overflowX: "scroll", padding: "16px 0" }, graph: { maxWidth: "974px", minWidth: "974px", marginLeft: "auto", marginRight: "auto" }, centerNode: { width: "244px" }, neighborNode: { width: "220px" } }); class Octopus extends React.Component { static defaultProps = { neighbors: {}, resource: {}, unmeshedSources: [] } static propTypes = { classes: PropTypes.shape({}).isRequired, 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 = _orderBy(neighbors.upstream, n => n.successRate); let downstreams = _orderBy(neighbors.downstream, n => n.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) { const { classes } = this.props; 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 => { const { classes } = this.props; return ( Unmeshed { unmeshedResources.map(r => { let display = displayName(r); return {display}; }) } ); } renderCollapsedNeighbors = neighbors => { const { classes } = this.props; return ( { neighbors.map(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 = _times(numNeighbors, i => i).map(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 = ( { arrowTypes.map(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, classes} = 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 (
{display.upstreams.displayed.map(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)} { display.downstreams.displayed.map(n => this.renderResourceCard(n, "neighbor"))} {_isEmpty(display.downstreams.collapsed) ? null : this.renderCollapsedNeighbors(display.downstreams.collapsed)}
); } } export default withStyles(styles)(Octopus);