From 7904235517b479f2f4b4216ba901882e35863ccc Mon Sep 17 00:00:00 2001 From: Max Fitton Date: Tue, 23 Jun 2020 13:11:32 -0700 Subject: [PATCH] Node Info Functional Components (#9073) --- .../pages/dashboard/node-info/NodeInfo.tsx | 342 ++++++++---------- .../dashboard/node-info/NodeRowGroup.tsx | 220 ++++++----- .../pages/dashboard/node-info/TotalRow.tsx | 82 ++--- 3 files changed, 299 insertions(+), 345 deletions(-) diff --git a/python/ray/dashboard/client/src/pages/dashboard/node-info/NodeInfo.tsx b/python/ray/dashboard/client/src/pages/dashboard/node-info/NodeInfo.tsx index 631b0dbf4..e987929ec 100644 --- a/python/ray/dashboard/client/src/pages/dashboard/node-info/NodeInfo.tsx +++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/NodeInfo.tsx @@ -1,5 +1,6 @@ import { createStyles, + makeStyles, Table, TableBody, TableCell, @@ -7,15 +8,12 @@ import { TableRow, Theme, Typography, - withStyles, - WithStyles, } from "@material-ui/core"; -import React from "react"; -import { connect } from "react-redux"; +import React, { useState } from "react"; +import { useSelector } from "react-redux"; import { RayletInfoResponse } from "../../../api"; import { sum } from "../../../common/util"; import { StoreState } from "../../../store"; -import {} from "../../../common/tableUtils"; import Errors from "./dialogs/errors/Errors"; import Logs from "./dialogs/logs/Logs"; import NodeRowGroup from "./NodeRowGroup"; @@ -39,7 +37,7 @@ const clusterWorkerPids = ( return nodeMap; }; -const styles = (theme: Theme) => +const useNodeInfoStyles = makeStyles((theme: Theme) => createStyles({ table: { marginTop: theme.spacing(1), @@ -51,187 +49,165 @@ const styles = (theme: Theme) => paddingRight: theme.spacing(1), }, }, - }); + }), +); -const mapStateToProps = (state: StoreState) => ({ +const nodeInfoSelector = (state: StoreState) => ({ nodeInfo: state.dashboard.nodeInfo, rayletInfo: state.dashboard.rayletInfo, }); -type State = { - logDialog: { hostname: string; pid: number | null } | null; - errorDialog: { hostname: string; pid: number | null } | null; +type dialogState = { + hostname: string; + pid: number | null; +} | null; + +const NodeInfo: React.FC<{}> = () => { + const [logDialog, setLogDialog] = useState(null); + const [errorDialog, setErrorDialog] = useState(null); + const classes = useNodeInfoStyles(); + const { nodeInfo, rayletInfo } = useSelector(nodeInfoSelector); + + if (nodeInfo === null || rayletInfo === null) { + return Loading...; + } + + const logCounts: { + [ip: string]: { + perWorker: { + [pid: string]: number; + }; + total: number; + }; + } = {}; + + const errorCounts: { + [ip: string]: { + perWorker: { + [pid: string]: number; + }; + total: number; + }; + } = {}; + + // We fetch data about which process IDs are registered with + // the cluster's raylet for each node. We use this to filter + // the worker data contained in the node info data because + // the node info can contain data from more than one cluster + // if more than one cluster is running on a machine. + const clusterWorkerPidsByIp = clusterWorkerPids(rayletInfo); + const clusterTotalWorkers = sum( + Array.from(clusterWorkerPidsByIp.values()).map( + (workerSet) => workerSet.size, + ), + ); + // Initialize inner structure of the count objects + for (const client of nodeInfo.clients) { + const clusterWorkerPids = clusterWorkerPidsByIp.get(client.ip); + if (!clusterWorkerPids) { + continue; + } + const filteredLogEntries = Object.entries( + nodeInfo.log_counts[client.ip] || {}, + ).filter(([pid, _]) => clusterWorkerPids.has(pid)); + const totalLogEntries = sum(filteredLogEntries.map(([_, count]) => count)); + logCounts[client.ip] = { + perWorker: Object.fromEntries(filteredLogEntries), + total: totalLogEntries, + }; + + const filteredErrEntries = Object.entries( + nodeInfo.error_counts[client.ip] || {}, + ).filter(([pid, _]) => clusterWorkerPids.has(pid)); + const totalErrEntries = sum(filteredErrEntries.map(([_, count]) => count)); + errorCounts[client.ip] = { + perWorker: Object.fromEntries(filteredErrEntries), + total: totalErrEntries, + }; + } + + return ( + + + + + + Host + Workers + Uptime + CPU + RAM + GPU + GRAM + Disk + Sent + Received + Logs + Errors + + + + {nodeInfo.clients.map((client) => { + const clusterWorkerPids = + clusterWorkerPidsByIp.get(client.ip) || new Set(); + return ( + + clusterWorkerPids.has(worker.pid.toString()), + ) + .sort((w1, w2) => { + if (w2.cmdline[0] === "ray::IDLE") { + return -1; + } + if (w1.cmdline[0] === "ray::IDLE") { + return 1; + } + return w1.pid < w2.pid ? -1 : 1; + })} + node={client} + raylet={ + client.ip in rayletInfo.nodes + ? rayletInfo.nodes[client.ip] + : null + } + logCounts={logCounts[client.ip]} + errorCounts={errorCounts[client.ip]} + setLogDialog={(hostname, pid) => + setLogDialog({ hostname, pid }) + } + setErrorDialog={(hostname, pid) => + setErrorDialog({ hostname, pid }) + } + initialExpanded={nodeInfo.clients.length <= 1} + /> + ); + })} + + +
+ {logDialog !== null && ( + setLogDialog(null)} + hostname={logDialog.hostname} + pid={logDialog.pid} + /> + )} + {errorDialog !== null && ( + setErrorDialog(null)} + hostname={errorDialog.hostname} + pid={errorDialog.pid} + /> + )} +
+ ); }; -class NodeInfo extends React.Component< - WithStyles & ReturnType -> { - state: State = { - logDialog: null, - errorDialog: null, - }; - - setLogDialog = (hostname: string, pid: number | null) => { - this.setState({ logDialog: { hostname, pid } }); - }; - - clearLogDialog = () => { - this.setState({ logDialog: null }); - }; - - setErrorDialog = (hostname: string, pid: number | null) => { - this.setState({ errorDialog: { hostname, pid } }); - }; - - clearErrorDialog = () => { - this.setState({ errorDialog: null }); - }; - - render() { - const { classes, nodeInfo, rayletInfo } = this.props; - const { logDialog, errorDialog } = this.state; - - if (nodeInfo === null || rayletInfo === null) { - return Loading...; - } - - const logCounts: { - [ip: string]: { - perWorker: { - [pid: string]: number; - }; - total: number; - }; - } = {}; - - const errorCounts: { - [ip: string]: { - perWorker: { - [pid: string]: number; - }; - total: number; - }; - } = {}; - - // We fetch data about which process IDs are registered with - // the cluster's raylet for each node. We use this to filter - // the worker data contained in the node info data because - // the node info can contain data from more than one cluster - // if more than one cluster is running on a machine. - const clusterWorkerPidsByIp = clusterWorkerPids(rayletInfo); - const clusterTotalWorkers = sum( - Array.from(clusterWorkerPidsByIp.values()).map( - (workerSet) => workerSet.size, - ), - ); - // Initialize inner structure of the count objects - for (const client of nodeInfo.clients) { - const clusterWorkerPids = clusterWorkerPidsByIp.get(client.ip); - if (!clusterWorkerPids) { - continue; - } - const filteredLogEntries = Object.entries( - nodeInfo.log_counts[client.ip] || {}, - ).filter(([pid, _]) => clusterWorkerPids.has(pid)); - const totalLogEntries = sum( - filteredLogEntries.map(([_, count]) => count), - ); - logCounts[client.ip] = { - perWorker: Object.fromEntries(filteredLogEntries), - total: totalLogEntries, - }; - - const filteredErrEntries = Object.entries( - nodeInfo.error_counts[client.ip] || {}, - ).filter(([pid, _]) => clusterWorkerPids.has(pid)); - const totalErrEntries = sum( - filteredErrEntries.map(([_, count]) => count), - ); - errorCounts[client.ip] = { - perWorker: Object.fromEntries(filteredErrEntries), - total: totalErrEntries, - }; - } - - return ( - - - - - - Host - Workers - Uptime - CPU - RAM - GPU - GRAM - Disk - Sent - Received - Logs - Errors - - - - {nodeInfo.clients.map((client) => { - const clusterWorkerPids = - clusterWorkerPidsByIp.get(client.ip) || new Set(); - return ( - - clusterWorkerPids.has(worker.pid.toString()), - ) - .sort((w1, w2) => { - if (w2.cmdline[0] === "ray::IDLE") { - return -1; - } - if (w1.cmdline[0] === "ray::IDLE") { - return 1; - } - return w1.pid < w2.pid ? -1 : 1; - })} - node={client} - raylet={ - client.ip in rayletInfo.nodes - ? rayletInfo.nodes[client.ip] - : null - } - logCounts={logCounts[client.ip]} - errorCounts={errorCounts[client.ip]} - setLogDialog={this.setLogDialog} - setErrorDialog={this.setErrorDialog} - initialExpanded={nodeInfo.clients.length <= 1} - /> - ); - })} - - -
- {logDialog !== null && ( - - )} - {errorDialog !== null && ( - - )} -
- ); - } -} - -export default connect(mapStateToProps)(withStyles(styles)(NodeInfo)); +export default NodeInfo; diff --git a/python/ray/dashboard/client/src/pages/dashboard/node-info/NodeRowGroup.tsx b/python/ray/dashboard/client/src/pages/dashboard/node-info/NodeRowGroup.tsx index 08bec78d1..58303c933 100644 --- a/python/ray/dashboard/client/src/pages/dashboard/node-info/NodeRowGroup.tsx +++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/NodeRowGroup.tsx @@ -1,15 +1,14 @@ import { createStyles, + makeStyles, TableCell, TableRow, Theme, - withStyles, - WithStyles, } from "@material-ui/core"; import AddIcon from "@material-ui/icons/Add"; import RemoveIcon from "@material-ui/icons/Remove"; import classNames from "classnames"; -import React from "react"; +import React, { useState } from "react"; import { NodeInfoResponse, NodeInfoResponseWorker, @@ -28,7 +27,7 @@ import { NodeSent, WorkerSent } from "./features/Sent"; import { NodeUptime, WorkerUptime } from "./features/Uptime"; import { NodeWorkers, WorkerWorkers } from "./features/Workers"; -const styles = (theme: Theme) => +const useNodeRowGroupStyles = makeStyles((theme: Theme) => createStyles({ cell: { padding: theme.spacing(1), @@ -49,12 +48,13 @@ const styles = (theme: Theme) => fontFamily: "SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace", whiteSpace: "pre", }, - }); + }), +); type ArrayType = T extends Array ? U : never; type Node = ArrayType; -type Props = { +type NodeRowGroupProps = { node: Node; clusterWorkers: Array; raylet: RayletInfoResponse["nodes"][keyof RayletInfoResponse["nodes"]] | null; @@ -71,118 +71,100 @@ type Props = { initialExpanded: boolean; }; -type State = { - expanded: boolean; +const NodeRowGroup: React.FC = ({ + node, + raylet, + clusterWorkers, + logCounts, + errorCounts, + setLogDialog, + setErrorDialog, + initialExpanded, +}) => { + const [expanded, setExpanded] = useState(initialExpanded); + const toggleExpand = () => setExpanded(!expanded); + const classes = useNodeRowGroupStyles(); + const features = [ + { NodeFeature: NodeHost, WorkerFeature: WorkerHost }, + { + NodeFeature: NodeWorkers(clusterWorkers.length), + WorkerFeature: WorkerWorkers, + }, + { NodeFeature: NodeUptime, WorkerFeature: WorkerUptime }, + { NodeFeature: NodeCPU, WorkerFeature: WorkerCPU }, + { NodeFeature: NodeRAM, WorkerFeature: WorkerRAM }, + { NodeFeature: NodeGPU, WorkerFeature: WorkerGPU }, + { NodeFeature: NodeGRAM, WorkerFeature: WorkerGRAM }, + { NodeFeature: NodeDisk, WorkerFeature: WorkerDisk }, + { NodeFeature: NodeSent, WorkerFeature: WorkerSent }, + { NodeFeature: NodeReceived, WorkerFeature: WorkerReceived }, + { + NodeFeature: makeNodeLogs(logCounts, setLogDialog), + WorkerFeature: makeWorkerLogs(logCounts, setLogDialog), + }, + { + NodeFeature: makeNodeErrors(errorCounts, setErrorDialog), + WorkerFeature: makeWorkerErrors(errorCounts, setErrorDialog), + }, + ]; + + return ( + + + + {!expanded ? ( + + ) : ( + + )} + + {features.map(({ NodeFeature }, index) => ( + + + + ))} + + {expanded && ( + + {raylet !== null && raylet.extraInfo !== undefined && ( + + + + {raylet.extraInfo} + + + )} + {clusterWorkers.map((worker, index: number) => { + const rayletWorker = + raylet?.workersStats.find( + (rayletWorker) => worker.pid === rayletWorker.pid, + ) || null; + + return ( + + + {features.map(({ WorkerFeature }, index) => ( + + + + ))} + + ); + })} + + )} + + ); }; -class NodeRowGroup extends React.Component< - Props & WithStyles, - State -> { - state: State = { - expanded: this.props.initialExpanded, - }; - - toggleExpand = () => { - this.setState((state) => ({ - expanded: !state.expanded, - })); - }; - - render() { - const { - classes, - node, - raylet, - clusterWorkers, - logCounts, - errorCounts, - setLogDialog, - setErrorDialog, - } = this.props; - const { expanded } = this.state; - const features = [ - { NodeFeature: NodeHost, WorkerFeature: WorkerHost }, - { - NodeFeature: NodeWorkers(clusterWorkers.length), - WorkerFeature: WorkerWorkers, - }, - { NodeFeature: NodeUptime, WorkerFeature: WorkerUptime }, - { NodeFeature: NodeCPU, WorkerFeature: WorkerCPU }, - { NodeFeature: NodeRAM, WorkerFeature: WorkerRAM }, - { NodeFeature: NodeGPU, WorkerFeature: WorkerGPU }, - { NodeFeature: NodeGRAM, WorkerFeature: WorkerGRAM }, - { NodeFeature: NodeDisk, WorkerFeature: WorkerDisk }, - { NodeFeature: NodeSent, WorkerFeature: WorkerSent }, - { NodeFeature: NodeReceived, WorkerFeature: WorkerReceived }, - { - NodeFeature: makeNodeLogs(logCounts, setLogDialog), - WorkerFeature: makeWorkerLogs(logCounts, setLogDialog), - }, - { - NodeFeature: makeNodeErrors(errorCounts, setErrorDialog), - WorkerFeature: makeWorkerErrors(errorCounts, setErrorDialog), - }, - ]; - - return ( - - - - {!expanded ? ( - - ) : ( - - )} - - {features.map(({ NodeFeature }, index) => ( - - - - ))} - - {expanded && ( - - {raylet !== null && raylet.extraInfo !== undefined && ( - - - - {raylet.extraInfo} - - - )} - {clusterWorkers.map((worker, index: number) => { - const rayletWorker = - raylet?.workersStats.find( - (rayletWorker) => worker.pid === rayletWorker.pid, - ) || null; - - return ( - - - {features.map(({ WorkerFeature }, index) => ( - - - - ))} - - ); - })} - - )} - - ); - } -} - -export default withStyles(styles)(NodeRowGroup); +export default NodeRowGroup; diff --git a/python/ray/dashboard/client/src/pages/dashboard/node-info/TotalRow.tsx b/python/ray/dashboard/client/src/pages/dashboard/node-info/TotalRow.tsx index 1c4297c56..36a95617a 100644 --- a/python/ray/dashboard/client/src/pages/dashboard/node-info/TotalRow.tsx +++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/TotalRow.tsx @@ -1,10 +1,9 @@ import { createStyles, + makeStyles, TableCell, TableRow, Theme, - WithStyles, - withStyles, } from "@material-ui/core"; import LayersIcon from "@material-ui/icons/Layers"; import React from "react"; @@ -22,7 +21,7 @@ import { ClusterSent } from "./features/Sent"; import { ClusterUptime } from "./features/Uptime"; import { ClusterWorkers } from "./features/Workers"; -const styles = (theme: Theme) => +const useTotalRowStyles = makeStyles((theme: Theme) => createStyles({ cell: { borderTopColor: theme.palette.divider, @@ -39,9 +38,10 @@ const styles = (theme: Theme) => fontSize: "1.5em", verticalAlign: "middle", }, - }); + }), +); -type Props = { +type TotalRowProps = { nodes: NodeInfoResponse["clients"]; clusterTotalWorkers: number; logCounts: { @@ -58,44 +58,40 @@ type Props = { }; }; -class TotalRow extends React.Component> { - render() { - const { - classes, - nodes, - clusterTotalWorkers, - logCounts, - errorCounts, - } = this.props; +const TotalRow: React.FC = ({ + nodes, + clusterTotalWorkers, + logCounts, + errorCounts, +}) => { + const classes = useTotalRowStyles(); + const features = [ + { ClusterFeature: ClusterHost }, + { ClusterFeature: ClusterWorkers(clusterTotalWorkers) }, + { ClusterFeature: ClusterUptime }, + { ClusterFeature: ClusterCPU }, + { ClusterFeature: ClusterRAM }, + { ClusterFeature: ClusterGPU }, + { ClusterFeature: ClusterGRAM }, + { ClusterFeature: ClusterDisk }, + { ClusterFeature: ClusterSent }, + { ClusterFeature: ClusterReceived }, + { ClusterFeature: makeClusterLogs(logCounts) }, + { ClusterFeature: makeClusterErrors(errorCounts) }, + ]; - const features = [ - { ClusterFeature: ClusterHost }, - { ClusterFeature: ClusterWorkers(clusterTotalWorkers) }, - { ClusterFeature: ClusterUptime }, - { ClusterFeature: ClusterCPU }, - { ClusterFeature: ClusterRAM }, - { ClusterFeature: ClusterGPU }, - { ClusterFeature: ClusterGRAM }, - { ClusterFeature: ClusterDisk }, - { ClusterFeature: ClusterSent }, - { ClusterFeature: ClusterReceived }, - { ClusterFeature: makeClusterLogs(logCounts) }, - { ClusterFeature: makeClusterErrors(errorCounts) }, - ]; - - return ( - - - + return ( + + + + + {features.map(({ ClusterFeature }, index) => ( + + - {features.map(({ ClusterFeature }, index) => ( - - - - ))} - - ); - } -} + ))} + + ); +}; -export default withStyles(styles)(TotalRow); +export default TotalRow;