From 1531c21dbd5aad281e4484b411295798f9c77eb8 Mon Sep 17 00:00:00 2001 From: Mitchell Stern Date: Mon, 16 Dec 2019 11:21:18 -0800 Subject: [PATCH] [Dashboard] Add remaining features from old dashboard (#6489) * [Dashboard] Add remaining features from old dashboard * Fix linting errors * Set cluster uptime statistic to N/A * Use proper singular or plural words for workers column * Ignore .js, .jsx, .ts, .tsx files in check-git-clang-format-output.sh * Fix bash quote issue --- ci/travis/check-git-clang-format-output.sh | 3 +- python/ray/dashboard/client/src/App.tsx | 4 +- python/ray/dashboard/client/src/api.ts | 13 +- .../client/src/common/NumberedLines.tsx | 4 +- .../dashboard/client/src/common/UsageBar.tsx | 12 +- .../client/src/common/formatUtils.ts | 19 +- .../client/src/pages/dashboard/Dashboard.tsx | 153 +--------------- .../{ => dashboard/dialogs}/errors/Errors.tsx | 6 +- .../{ => dashboard/dialogs}/logs/Logs.tsx | 6 +- .../src/pages/dashboard/features/CPU.tsx | 18 -- .../src/pages/dashboard/features/Disk.tsx | 20 --- .../src/pages/dashboard/features/RAM.tsx | 18 -- .../src/pages/dashboard/features/Uptime.tsx | 11 -- .../src/pages/dashboard/features/Workers.tsx | 13 -- .../pages/dashboard/node-info/LastUpdated.tsx | 51 ++++++ .../pages/dashboard/node-info/NodeInfo.tsx | 167 ++++++++++++++++++ .../{ => node-info}/NodeRowGroup.tsx | 14 +- .../pages/dashboard/node-info/TotalRow.tsx | 87 +++++++++ .../dashboard/node-info/features/CPU.tsx | 55 ++++++ .../dashboard/node-info/features/Disk.tsx | 37 ++++ .../{ => node-info}/features/Errors.tsx | 32 +++- .../{ => node-info}/features/Host.tsx | 13 +- .../{ => node-info}/features/Logs.tsx | 31 +++- .../dashboard/node-info/features/RAM.tsx | 37 ++++ .../dashboard/node-info/features/Received.tsx | 30 ++++ .../dashboard/node-info/features/Sent.tsx | 28 +++ .../dashboard/node-info/features/Uptime.tsx | 26 +++ .../dashboard/node-info/features/Workers.tsx | 40 +++++ .../{ => node-info}/features/types.tsx | 6 +- .../pages/dashboard/ray-config/RayConfig.tsx | 132 ++++++++++++++ .../client/src/pages/dashboard/state.ts | 9 +- python/ray/dashboard/dashboard.py | 53 +----- python/ray/reporter.py | 4 +- 33 files changed, 838 insertions(+), 314 deletions(-) rename python/ray/dashboard/client/src/pages/{ => dashboard/dialogs}/errors/Errors.tsx (93%) rename python/ray/dashboard/client/src/pages/{ => dashboard/dialogs}/logs/Logs.tsx (93%) delete mode 100644 python/ray/dashboard/client/src/pages/dashboard/features/CPU.tsx delete mode 100644 python/ray/dashboard/client/src/pages/dashboard/features/Disk.tsx delete mode 100644 python/ray/dashboard/client/src/pages/dashboard/features/RAM.tsx delete mode 100644 python/ray/dashboard/client/src/pages/dashboard/features/Uptime.tsx delete mode 100644 python/ray/dashboard/client/src/pages/dashboard/features/Workers.tsx create mode 100644 python/ray/dashboard/client/src/pages/dashboard/node-info/LastUpdated.tsx create mode 100644 python/ray/dashboard/client/src/pages/dashboard/node-info/NodeInfo.tsx rename python/ray/dashboard/client/src/pages/dashboard/{ => node-info}/NodeRowGroup.tsx (87%) create mode 100644 python/ray/dashboard/client/src/pages/dashboard/node-info/TotalRow.tsx create mode 100644 python/ray/dashboard/client/src/pages/dashboard/node-info/features/CPU.tsx create mode 100644 python/ray/dashboard/client/src/pages/dashboard/node-info/features/Disk.tsx rename python/ray/dashboard/client/src/pages/dashboard/{ => node-info}/features/Errors.tsx (59%) rename python/ray/dashboard/client/src/pages/dashboard/{ => node-info}/features/Host.tsx (67%) rename python/ray/dashboard/client/src/pages/dashboard/{ => node-info}/features/Logs.tsx (62%) create mode 100644 python/ray/dashboard/client/src/pages/dashboard/node-info/features/RAM.tsx create mode 100644 python/ray/dashboard/client/src/pages/dashboard/node-info/features/Received.tsx create mode 100644 python/ray/dashboard/client/src/pages/dashboard/node-info/features/Sent.tsx create mode 100644 python/ray/dashboard/client/src/pages/dashboard/node-info/features/Uptime.tsx create mode 100644 python/ray/dashboard/client/src/pages/dashboard/node-info/features/Workers.tsx rename python/ray/dashboard/client/src/pages/dashboard/{ => node-info}/features/types.tsx (70%) create mode 100644 python/ray/dashboard/client/src/pages/dashboard/ray-config/RayConfig.tsx diff --git a/ci/travis/check-git-clang-format-output.sh b/ci/travis/check-git-clang-format-output.sh index 6d83044c4..224de1fc3 100755 --- a/ci/travis/check-git-clang-format-output.sh +++ b/ci/travis/check-git-clang-format-output.sh @@ -8,7 +8,8 @@ else base_commit="$TRAVIS_BRANCH" echo "Running clang-format against branch $base_commit, with hash $(git rev-parse $base_commit)" fi -output="$(ci/travis/git-clang-format --binary clang-format --commit $base_commit --diff --exclude '(.*thirdparty/|.*redismodule.h|.*.js|.*.java)')" +exclude_regex="(.*thirdparty/|.*redismodule.h|.*.java|.*.jsx?|.*.tsx?)" +output=$(ci/travis/git-clang-format --binary clang-format --commit $base_commit --diff --exclude "$exclude_regex") if [ "$output" == "no modified files to format" ] || [ "$output" == "clang-format did not modify any files" ] ; then echo "clang-format passed." exit 0 diff --git a/python/ray/dashboard/client/src/App.tsx b/python/ray/dashboard/client/src/App.tsx index ccd6c3c99..6e9bd2d2f 100644 --- a/python/ray/dashboard/client/src/App.tsx +++ b/python/ray/dashboard/client/src/App.tsx @@ -3,8 +3,8 @@ import React from "react"; import { Provider } from "react-redux"; import { BrowserRouter, Route } from "react-router-dom"; import Dashboard from "./pages/dashboard/Dashboard"; -import Errors from "./pages/errors/Errors"; -import Logs from "./pages/logs/Logs"; +import Errors from "./pages/dashboard/dialogs/errors/Errors"; +import Logs from "./pages/dashboard/dialogs/logs/Logs"; import { store } from "./store"; class App extends React.Component { diff --git a/python/ray/dashboard/client/src/api.ts b/python/ray/dashboard/client/src/api.ts index ad40549c2..d9b4167a8 100644 --- a/python/ray/dashboard/client/src/api.ts +++ b/python/ray/dashboard/client/src/api.ts @@ -22,6 +22,18 @@ const get = async (path: string, params: { [key: string]: any }) => { return result as T; }; +export interface RayConfigResponse { + min_workers: number; + max_workers: number; + initial_workers: number; + autoscaling_mode: string; + idle_timeout_minutes: number; + head_type: string; + worker_type: string; +} + +export const getRayConfig = () => get("/api/ray_config", {}); + export interface NodeInfoResponse { clients: Array<{ now: number; @@ -44,7 +56,6 @@ export interface NodeInfoResponse { workers: Array<{ pid: number; create_time: number; - name: string; cmdline: string[]; cpu_percent: number; cpu_times: { diff --git a/python/ray/dashboard/client/src/common/NumberedLines.tsx b/python/ray/dashboard/client/src/common/NumberedLines.tsx index 4a51697d2..ce5e5f28e 100644 --- a/python/ray/dashboard/client/src/common/NumberedLines.tsx +++ b/python/ray/dashboard/client/src/common/NumberedLines.tsx @@ -43,7 +43,7 @@ interface Props { lines: string[]; } -class Component extends React.Component> { +class NumberedLines extends React.Component> { render() { const { classes, lines } = this.props; return ( @@ -66,4 +66,4 @@ class Component extends React.Component> { } } -export default withStyles(styles)(Component); +export default withStyles(styles)(NumberedLines); diff --git a/python/ray/dashboard/client/src/common/UsageBar.tsx b/python/ray/dashboard/client/src/common/UsageBar.tsx index 04007b91d..953ce8236 100644 --- a/python/ray/dashboard/client/src/common/UsageBar.tsx +++ b/python/ray/dashboard/client/src/common/UsageBar.tsx @@ -19,6 +19,10 @@ const styles = (theme: Theme) => borderColor: theme.palette.divider, borderStyle: "solid", borderWidth: 1 + }, + inner: { + paddingLeft: theme.spacing(1), + paddingRight: theme.spacing(1) } }); @@ -27,7 +31,7 @@ interface Props { text: string; } -class Component extends React.Component> { +class UsageBar extends React.Component> { render() { const { classes, text } = this.props; @@ -55,10 +59,12 @@ class Component extends React.Component> { // gradient background otherwise. return (
-
{text}
+
+ {text} +
); } } -export default withStyles(styles)(Component); +export default withStyles(styles)(UsageBar); diff --git a/python/ray/dashboard/client/src/common/formatUtils.ts b/python/ray/dashboard/client/src/common/formatUtils.ts index 71bcb913d..e0dfe39cd 100644 --- a/python/ray/dashboard/client/src/common/formatUtils.ts +++ b/python/ray/dashboard/client/src/common/formatUtils.ts @@ -17,17 +17,16 @@ export const formatUsage = ( return `${usedFormatted} / ${totalFormatted} (${percent.toFixed(0)}%)`; }; -export const formatUptime = (bootTime: number) => { - const uptimeSecondsTotal = Date.now() / 1000 - bootTime; - const uptimeSeconds = Math.floor(uptimeSecondsTotal) % 60; - const uptimeMinutes = Math.floor(uptimeSecondsTotal / 60) % 60; - const uptimeHours = Math.floor(uptimeSecondsTotal / 60 / 60) % 24; - const uptimeDays = Math.floor(uptimeSecondsTotal / 60 / 60 / 24); +export const formatDuration = (durationInSeconds: number) => { + const durationSeconds = Math.floor(durationInSeconds) % 60; + const durationMinutes = Math.floor(durationInSeconds / 60) % 60; + const durationHours = Math.floor(durationInSeconds / 60 / 60) % 24; + const durationDays = Math.floor(durationInSeconds / 60 / 60 / 24); const pad = (value: number) => value.toString().padStart(2, "0"); return [ - uptimeDays ? `${uptimeDays}d` : "", - `${pad(uptimeHours)}h`, - `${pad(uptimeMinutes)}m`, - `${pad(uptimeSeconds)}s` + durationDays ? `${durationDays}d` : "", + `${pad(durationHours)}h`, + `${pad(durationMinutes)}m`, + `${pad(durationSeconds)}s` ].join(" "); }; diff --git a/python/ray/dashboard/client/src/pages/dashboard/Dashboard.tsx b/python/ray/dashboard/client/src/pages/dashboard/Dashboard.tsx index ba0d316bf..899ac5518 100644 --- a/python/ray/dashboard/client/src/pages/dashboard/Dashboard.tsx +++ b/python/ray/dashboard/client/src/pages/dashboard/Dashboard.tsx @@ -1,18 +1,10 @@ import { Theme } from "@material-ui/core/styles/createMuiTheme"; import createStyles from "@material-ui/core/styles/createStyles"; import withStyles, { WithStyles } from "@material-ui/core/styles/withStyles"; -import Table from "@material-ui/core/Table"; -import TableBody from "@material-ui/core/TableBody"; -import TableCell from "@material-ui/core/TableCell"; -import TableHead from "@material-ui/core/TableHead"; -import TableRow from "@material-ui/core/TableRow"; import Typography from "@material-ui/core/Typography"; import React from "react"; -import { connect } from "react-redux"; -import { getNodeInfo } from "../../api"; -import { StoreState } from "../../store"; -import NodeRowGroup from "./NodeRowGroup"; -import { dashboardActions } from "./state"; +import NodeInfo from "./node-info/NodeInfo"; +import RayConfig from "./ray-config/RayConfig"; const styles = (theme: Theme) => createStyles({ @@ -20,151 +12,22 @@ const styles = (theme: Theme) => backgroundColor: theme.palette.background.paper, padding: theme.spacing(2), "& > :not(:first-child)": { - marginTop: theme.spacing(2) - } - }, - cell: { - padding: theme.spacing(1), - textAlign: "center", - "&:last-child": { - paddingRight: theme.spacing(1) + marginTop: theme.spacing(4) } } }); -const mapStateToProps = (state: StoreState) => ({ - nodeInfo: state.dashboard.nodeInfo, - lastUpdatedAt: state.dashboard.lastUpdatedAt, - error: state.dashboard.error -}); - -const mapDispatchToProps = dashboardActions; - -class Dashboard extends React.Component< - WithStyles & - ReturnType & - typeof mapDispatchToProps -> { - refresh = async () => { - try { - const nodeInfo = await getNodeInfo(); - this.props.setNodeInfo(nodeInfo); - } catch (error) { - this.props.setError(error.toString()); - } finally { - setTimeout(this.refresh, 1000); - } - }; - - async componentDidMount() { - await this.refresh(); - } - +class Dashboard extends React.Component> { render() { - const { classes, nodeInfo, lastUpdatedAt, error } = this.props; - - if (error !== null) { - return ( - - {error} - - ); - } - - if (nodeInfo === null) { - return ( - - Loading... - - ); - } - - const logCounts: { - [ip: string]: { - perWorker: { - [pid: string]: number; - }; - total: number; - }; - } = {}; - - const errorCounts: { - [ip: string]: { - perWorker: { - [pid: string]: number; - }; - total: number; - }; - } = {}; - - for (const client of nodeInfo.clients) { - logCounts[client.ip] = { perWorker: {}, total: 0 }; - errorCounts[client.ip] = { perWorker: {}, total: 0 }; - for (const worker of client.workers) { - logCounts[client.ip].perWorker[worker.pid] = 0; - errorCounts[client.ip].perWorker[worker.pid] = 0; - } - } - - for (const ip of Object.keys(nodeInfo.log_counts)) { - if (ip in logCounts) { - for (const [pid, count] of Object.entries(nodeInfo.log_counts[ip])) { - logCounts[ip].perWorker[pid] = count; - logCounts[ip].total += count; - } - } - } - - for (const ip of Object.keys(nodeInfo.error_counts)) { - if (ip in errorCounts) { - for (const [pid, count] of Object.entries(nodeInfo.error_counts[ip])) { - errorCounts[ip].perWorker[pid] = count; - errorCounts[ip].total += count; - } - } - } - + const { classes } = this.props; return (
Ray Dashboard - - - - - Host - Workers - Uptime - CPU - RAM - Disk - {/*Sent*/} - {/*Received*/} - Logs - Errors - - - - {nodeInfo.clients.map(client => ( - - ))} - -
- {lastUpdatedAt !== null && ( - - Last updated: {new Date(lastUpdatedAt).toLocaleString()} - - )} + +
); } } -export default connect( - mapStateToProps, - mapDispatchToProps -)(withStyles(styles)(Dashboard)); +export default withStyles(styles)(Dashboard); diff --git a/python/ray/dashboard/client/src/pages/errors/Errors.tsx b/python/ray/dashboard/client/src/pages/dashboard/dialogs/errors/Errors.tsx similarity index 93% rename from python/ray/dashboard/client/src/pages/errors/Errors.tsx rename to python/ray/dashboard/client/src/pages/dashboard/dialogs/errors/Errors.tsx index 728afe872..97aef98c2 100644 --- a/python/ray/dashboard/client/src/pages/errors/Errors.tsx +++ b/python/ray/dashboard/client/src/pages/dashboard/dialogs/errors/Errors.tsx @@ -5,9 +5,9 @@ import withStyles, { WithStyles } from "@material-ui/core/styles/withStyles"; import Typography from "@material-ui/core/Typography"; import React from "react"; import { RouteComponentProps } from "react-router"; -import { ErrorsResponse, getErrors } from "../../api"; -import DialogWithTitle from "../../common/DialogWithTitle"; -import NumberedLines from "../../common/NumberedLines"; +import { ErrorsResponse, getErrors } from "../../../../api"; +import DialogWithTitle from "../../../../common/DialogWithTitle"; +import NumberedLines from "../../../../common/NumberedLines"; const styles = (theme: Theme) => createStyles({ diff --git a/python/ray/dashboard/client/src/pages/logs/Logs.tsx b/python/ray/dashboard/client/src/pages/dashboard/dialogs/logs/Logs.tsx similarity index 93% rename from python/ray/dashboard/client/src/pages/logs/Logs.tsx rename to python/ray/dashboard/client/src/pages/dashboard/dialogs/logs/Logs.tsx index 4059c42a7..52b625567 100644 --- a/python/ray/dashboard/client/src/pages/logs/Logs.tsx +++ b/python/ray/dashboard/client/src/pages/dashboard/dialogs/logs/Logs.tsx @@ -5,9 +5,9 @@ import withStyles, { WithStyles } from "@material-ui/core/styles/withStyles"; import Typography from "@material-ui/core/Typography"; import React from "react"; import { RouteComponentProps } from "react-router"; -import { getLogs, LogsResponse } from "../../api"; -import DialogWithTitle from "../../common/DialogWithTitle"; -import NumberedLines from "../../common/NumberedLines"; +import { getLogs, LogsResponse } from "../../../../api"; +import DialogWithTitle from "../../../../common/DialogWithTitle"; +import NumberedLines from "../../../../common/NumberedLines"; const styles = (theme: Theme) => createStyles({ diff --git a/python/ray/dashboard/client/src/pages/dashboard/features/CPU.tsx b/python/ray/dashboard/client/src/pages/dashboard/features/CPU.tsx deleted file mode 100644 index 1ccd4179b..000000000 --- a/python/ray/dashboard/client/src/pages/dashboard/features/CPU.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from "react"; -import UsageBar from "../../../common/UsageBar"; -import { NodeFeatureComponent, WorkerFeatureComponent } from "./types"; - -export const NodeCPU: NodeFeatureComponent = ({ node }) => ( -
- -
-); - -export const WorkerCPU: WorkerFeatureComponent = ({ worker }) => ( -
- -
-); diff --git a/python/ray/dashboard/client/src/pages/dashboard/features/Disk.tsx b/python/ray/dashboard/client/src/pages/dashboard/features/Disk.tsx deleted file mode 100644 index f26c9b5c5..000000000 --- a/python/ray/dashboard/client/src/pages/dashboard/features/Disk.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import Typography from "@material-ui/core/Typography"; -import React from "react"; -import { formatUsage } from "../../../common/formatUtils"; -import UsageBar from "../../../common/UsageBar"; -import { NodeFeatureComponent, WorkerFeatureComponent } from "./types"; - -export const NodeDisk: NodeFeatureComponent = ({ node }) => ( - - - -); - -export const WorkerDisk: WorkerFeatureComponent = () => ( - - Not available - -); diff --git a/python/ray/dashboard/client/src/pages/dashboard/features/RAM.tsx b/python/ray/dashboard/client/src/pages/dashboard/features/RAM.tsx deleted file mode 100644 index 363f3ba66..000000000 --- a/python/ray/dashboard/client/src/pages/dashboard/features/RAM.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from "react"; -import { formatByteAmount, formatUsage } from "../../../common/formatUtils"; -import UsageBar from "../../../common/UsageBar"; -import { NodeFeatureComponent, WorkerFeatureComponent } from "./types"; - -export const NodeRAM: NodeFeatureComponent = ({ node }) => ( - -); - -export const WorkerRAM: WorkerFeatureComponent = ({ node, worker }) => ( - -); diff --git a/python/ray/dashboard/client/src/pages/dashboard/features/Uptime.tsx b/python/ray/dashboard/client/src/pages/dashboard/features/Uptime.tsx deleted file mode 100644 index c70988129..000000000 --- a/python/ray/dashboard/client/src/pages/dashboard/features/Uptime.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; -import { formatUptime } from "../../../common/formatUtils"; -import { NodeFeatureComponent, WorkerFeatureComponent } from "./types"; - -export const NodeUptime: NodeFeatureComponent = ({ node }) => ( - {formatUptime(node.boot_time)} -); - -export const WorkerUptime: WorkerFeatureComponent = ({ worker }) => ( - {formatUptime(worker.create_time)} -); diff --git a/python/ray/dashboard/client/src/pages/dashboard/features/Workers.tsx b/python/ray/dashboard/client/src/pages/dashboard/features/Workers.tsx deleted file mode 100644 index 5fc064c5f..000000000 --- a/python/ray/dashboard/client/src/pages/dashboard/features/Workers.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react"; -import { NodeFeatureComponent, WorkerFeatureComponent } from "./types"; - -export const NodeWorkers: NodeFeatureComponent = ({ node }) => ( - {node.workers.length} -); - -// Ray worker process titles have one of the following forms: `ray::IDLE`, -// `ray::function()`, `ray::Class`, or `ray::Class.method()`. We extract the -// second portion here for display in the "Workers" column. -export const WorkerWorkers: WorkerFeatureComponent = ({ worker }) => ( - {worker.cmdline[0].split("::", 2)[1]} -); diff --git a/python/ray/dashboard/client/src/pages/dashboard/node-info/LastUpdated.tsx b/python/ray/dashboard/client/src/pages/dashboard/node-info/LastUpdated.tsx new file mode 100644 index 000000000..130178c05 --- /dev/null +++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/LastUpdated.tsx @@ -0,0 +1,51 @@ +import { Theme } from "@material-ui/core/styles/createMuiTheme"; +import createStyles from "@material-ui/core/styles/createStyles"; +import withStyles, { WithStyles } from "@material-ui/core/styles/withStyles"; +import Typography from "@material-ui/core/Typography"; +import React from "react"; +import { connect } from "react-redux"; +import { StoreState } from "../../../store"; + +const styles = (theme: Theme) => + createStyles({ + root: { + marginTop: theme.spacing(2) + }, + lastUpdated: { + color: theme.palette.text.secondary, + fontSize: "0.8125rem", + textAlign: "center" + }, + error: { + color: theme.palette.error.main, + fontSize: "0.8125rem", + textAlign: "center" + } + }); + +const mapStateToProps = (state: StoreState) => ({ + lastUpdatedAt: state.dashboard.lastUpdatedAt, + error: state.dashboard.error +}); + +class LastUpdated extends React.Component< + WithStyles & ReturnType +> { + render() { + const { classes, lastUpdatedAt, error } = this.props; + return ( +
+ {lastUpdatedAt !== null && ( + + Last updated: {new Date(lastUpdatedAt).toLocaleString()} + + )} + {error !== null && ( + {error} + )} +
+ ); + } +} + +export default connect(mapStateToProps)(withStyles(styles)(LastUpdated)); 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 new file mode 100644 index 000000000..2e6007b03 --- /dev/null +++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/NodeInfo.tsx @@ -0,0 +1,167 @@ +import { Theme } from "@material-ui/core/styles/createMuiTheme"; +import createStyles from "@material-ui/core/styles/createStyles"; +import withStyles, { WithStyles } from "@material-ui/core/styles/withStyles"; +import Table from "@material-ui/core/Table"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableHead from "@material-ui/core/TableHead"; +import TableRow from "@material-ui/core/TableRow"; +import Typography from "@material-ui/core/Typography"; +import React from "react"; +import { connect } from "react-redux"; +import { getNodeInfo } from "../../../api"; +import { StoreState } from "../../../store"; +import { dashboardActions } from "../state"; +import LastUpdated from "./LastUpdated"; +import NodeRowGroup from "./NodeRowGroup"; +import TotalRow from "./TotalRow"; + +const styles = (theme: Theme) => + createStyles({ + root: { + backgroundColor: theme.palette.background.paper, + padding: theme.spacing(2), + "& > :not(:first-child)": { + marginTop: theme.spacing(2) + } + }, + table: { + marginTop: theme.spacing(1) + }, + cell: { + padding: theme.spacing(1), + textAlign: "center", + "&:last-child": { + paddingRight: theme.spacing(1) + } + } + }); + +const mapStateToProps = (state: StoreState) => ({ + nodeInfo: state.dashboard.nodeInfo +}); + +const mapDispatchToProps = dashboardActions; + +class NodeInfo extends React.Component< + WithStyles & + ReturnType & + typeof mapDispatchToProps +> { + refreshNodeInfo = async () => { + try { + const nodeInfo = await getNodeInfo(); + this.props.setNodeInfo(nodeInfo); + this.props.setError(null); + } catch (error) { + this.props.setError(error.toString()); + } finally { + setTimeout(this.refreshNodeInfo, 1000); + } + }; + + async componentDidMount() { + await this.refreshNodeInfo(); + } + + render() { + const { classes, nodeInfo } = this.props; + + if (nodeInfo === null) { + return ( + + Loading... + + ); + } + + const logCounts: { + [ip: string]: { + perWorker: { + [pid: string]: number; + }; + total: number; + }; + } = {}; + + const errorCounts: { + [ip: string]: { + perWorker: { + [pid: string]: number; + }; + total: number; + }; + } = {}; + + for (const client of nodeInfo.clients) { + logCounts[client.ip] = { perWorker: {}, total: 0 }; + errorCounts[client.ip] = { perWorker: {}, total: 0 }; + for (const worker of client.workers) { + logCounts[client.ip].perWorker[worker.pid] = 0; + errorCounts[client.ip].perWorker[worker.pid] = 0; + } + } + + for (const ip of Object.keys(nodeInfo.log_counts)) { + if (ip in logCounts) { + for (const [pid, count] of Object.entries(nodeInfo.log_counts[ip])) { + logCounts[ip].perWorker[pid] = count; + logCounts[ip].total += count; + } + } + } + + for (const ip of Object.keys(nodeInfo.error_counts)) { + if (ip in errorCounts) { + for (const [pid, count] of Object.entries(nodeInfo.error_counts[ip])) { + errorCounts[ip].perWorker[pid] = count; + errorCounts[ip].total += count; + } + } + } + + return ( +
+ Node information: + + + + + Host + Workers + Uptime + CPU + RAM + Disk + Sent + Received + Logs + Errors + + + + {nodeInfo.clients.map(client => ( + + ))} + + +
+ +
+ ); + } +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(withStyles(styles)(NodeInfo)); diff --git a/python/ray/dashboard/client/src/pages/dashboard/NodeRowGroup.tsx b/python/ray/dashboard/client/src/pages/dashboard/node-info/NodeRowGroup.tsx similarity index 87% rename from python/ray/dashboard/client/src/pages/dashboard/NodeRowGroup.tsx rename to python/ray/dashboard/client/src/pages/dashboard/node-info/NodeRowGroup.tsx index eb5f907ac..8aa0aa5e6 100644 --- a/python/ray/dashboard/client/src/pages/dashboard/NodeRowGroup.tsx +++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/NodeRowGroup.tsx @@ -7,13 +7,15 @@ import AddIcon from "@material-ui/icons/Add"; import RemoveIcon from "@material-ui/icons/Remove"; import classNames from "classnames"; import React from "react"; -import { NodeInfoResponse } from "../../api"; +import { NodeInfoResponse } from "../../../api"; import { NodeCPU, WorkerCPU } from "./features/CPU"; import { NodeDisk, WorkerDisk } from "./features/Disk"; import { makeNodeErrors, makeWorkerErrors } from "./features/Errors"; import { NodeHost, WorkerHost } from "./features/Host"; import { makeNodeLogs, makeWorkerLogs } from "./features/Logs"; import { NodeRAM, WorkerRAM } from "./features/RAM"; +import { NodeReceived, WorkerReceived } from "./features/Received"; +import { NodeSent, WorkerSent } from "./features/Sent"; import { NodeUptime, WorkerUptime } from "./features/Uptime"; import { NodeWorkers, WorkerWorkers } from "./features/Workers"; @@ -80,6 +82,8 @@ class NodeRowGroup extends React.Component< { NodeFeature: NodeCPU, WorkerFeature: WorkerCPU }, { NodeFeature: NodeRAM, WorkerFeature: WorkerRAM }, { NodeFeature: NodeDisk, WorkerFeature: WorkerDisk }, + { NodeFeature: NodeSent, WorkerFeature: WorkerSent }, + { NodeFeature: NodeReceived, WorkerFeature: WorkerReceived }, { NodeFeature: makeNodeLogs(logCounts), WorkerFeature: makeWorkerLogs(logCounts) @@ -103,8 +107,8 @@ class NodeRowGroup extends React.Component< )} - {features.map(({ NodeFeature }) => ( - + {features.map(({ NodeFeature }, index) => ( + ))} @@ -113,8 +117,8 @@ class NodeRowGroup extends React.Component< node.workers.map((worker, index: number) => ( - {features.map(({ WorkerFeature }) => ( - + {features.map(({ WorkerFeature }, index) => ( + ))} 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 new file mode 100644 index 000000000..fc587a26f --- /dev/null +++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/TotalRow.tsx @@ -0,0 +1,87 @@ +import { Theme } from "@material-ui/core/styles/createMuiTheme"; +import createStyles from "@material-ui/core/styles/createStyles"; +import withStyles, { WithStyles } from "@material-ui/core/styles/withStyles"; +import TableCell from "@material-ui/core/TableCell"; +import TableRow from "@material-ui/core/TableRow"; +import LayersIcon from "@material-ui/icons/Layers"; +import React from "react"; +import { NodeInfoResponse } from "../../../api"; +import { ClusterCPU } from "./features/CPU"; +import { ClusterDisk } from "./features/Disk"; +import { makeClusterErrors } from "./features/Errors"; +import { ClusterHost } from "./features/Host"; +import { makeClusterLogs } from "./features/Logs"; +import { ClusterRAM } from "./features/RAM"; +import { ClusterReceived } from "./features/Received"; +import { ClusterSent } from "./features/Sent"; +import { ClusterUptime } from "./features/Uptime"; +import { ClusterWorkers } from "./features/Workers"; + +const styles = (theme: Theme) => + createStyles({ + cell: { + borderTopColor: theme.palette.divider, + borderTopStyle: "solid", + borderTopWidth: 2, + padding: theme.spacing(1), + textAlign: "center", + "&:last-child": { + paddingRight: theme.spacing(1) + } + }, + totalIcon: { + color: theme.palette.text.secondary, + fontSize: "1.5em", + verticalAlign: "middle" + } + }); + +interface Props { + nodes: NodeInfoResponse["clients"]; + logCounts: { + [ip: string]: { + perWorker: { [pid: string]: number }; + total: number; + }; + }; + errorCounts: { + [ip: string]: { + perWorker: { [pid: string]: number }; + total: number; + }; + }; +} + +class TotalRow extends React.Component> { + render() { + const { classes, nodes, logCounts, errorCounts } = this.props; + + const features = [ + { ClusterFeature: ClusterHost }, + { ClusterFeature: ClusterWorkers }, + { ClusterFeature: ClusterUptime }, + { ClusterFeature: ClusterCPU }, + { ClusterFeature: ClusterRAM }, + { ClusterFeature: ClusterDisk }, + { ClusterFeature: ClusterSent }, + { ClusterFeature: ClusterReceived }, + { ClusterFeature: makeClusterLogs(logCounts) }, + { ClusterFeature: makeClusterErrors(errorCounts) } + ]; + + return ( + + + + + {features.map(({ ClusterFeature }, index) => ( + + + + ))} + + ); + } +} + +export default withStyles(styles)(TotalRow); diff --git a/python/ray/dashboard/client/src/pages/dashboard/node-info/features/CPU.tsx b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/CPU.tsx new file mode 100644 index 000000000..da7f4a80d --- /dev/null +++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/CPU.tsx @@ -0,0 +1,55 @@ +import React from "react"; +import UsageBar from "../../../../common/UsageBar"; +import { + ClusterFeatureComponent, + NodeFeatureComponent, + WorkerFeatureComponent +} from "./types"; + +const getWeightedAverage = ( + input: { + weight: number; + value: number; + }[] +) => { + if (input.length === 0) { + return 0; + } + + let totalWeightTimesValue = 0; + let totalWeight = 0; + for (const { weight, value } of input) { + totalWeightTimesValue += weight * value; + totalWeight += weight; + } + return totalWeightTimesValue / totalWeight; +}; + +export const ClusterCPU: ClusterFeatureComponent = ({ nodes }) => { + const cpuWeightedAverage = getWeightedAverage( + nodes.map(node => ({ weight: node.cpus[0], value: node.cpu })) + ); + return ( +
+ +
+ ); +}; + +export const NodeCPU: NodeFeatureComponent = ({ node }) => ( +
+ +
+); + +export const WorkerCPU: WorkerFeatureComponent = ({ worker }) => ( +
+ +
+); diff --git a/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Disk.tsx b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Disk.tsx new file mode 100644 index 000000000..dc281b09a --- /dev/null +++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Disk.tsx @@ -0,0 +1,37 @@ +import Typography from "@material-ui/core/Typography"; +import React from "react"; +import { formatUsage } from "../../../../common/formatUtils"; +import UsageBar from "../../../../common/UsageBar"; +import { + ClusterFeatureComponent, + NodeFeatureComponent, + WorkerFeatureComponent +} from "./types"; + +export const ClusterDisk: ClusterFeatureComponent = ({ nodes }) => { + let used = 0; + let total = 0; + for (const node of nodes) { + used += node.disk["/"].used; + total += node.disk["/"].total; + } + return ( + + ); +}; + +export const NodeDisk: NodeFeatureComponent = ({ node }) => ( + +); + +export const WorkerDisk: WorkerFeatureComponent = () => ( + + N/A + +); diff --git a/python/ray/dashboard/client/src/pages/dashboard/features/Errors.tsx b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Errors.tsx similarity index 59% rename from python/ray/dashboard/client/src/pages/dashboard/features/Errors.tsx rename to python/ray/dashboard/client/src/pages/dashboard/node-info/features/Errors.tsx index 2ec7095e5..9556e24bc 100644 --- a/python/ray/dashboard/client/src/pages/dashboard/features/Errors.tsx +++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Errors.tsx @@ -2,7 +2,37 @@ import Link from "@material-ui/core/Link"; import Typography from "@material-ui/core/Typography"; import React from "react"; import { Link as RouterLink } from "react-router-dom"; -import { NodeFeatureComponent, WorkerFeatureComponent } from "./types"; +import { + ClusterFeatureComponent, + NodeFeatureComponent, + WorkerFeatureComponent +} from "./types"; + +export const makeClusterErrors = (errorCounts: { + [ip: string]: { + perWorker: { + [pid: string]: number; + }; + total: number; + }; +}): ClusterFeatureComponent => ({ nodes }) => { + let totalErrorCount = 0; + for (const node of nodes) { + if (node.ip in errorCounts) { + totalErrorCount += errorCounts[node.ip].total; + } + } + return totalErrorCount === 0 ? ( + + No errors + + ) : ( + + {totalErrorCount.toLocaleString()}{" "} + {totalErrorCount === 1 ? "error" : "errors"} + + ); +}; export const makeNodeErrors = (errorCounts: { perWorker: { [pid: string]: number }; diff --git a/python/ray/dashboard/client/src/pages/dashboard/features/Host.tsx b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Host.tsx similarity index 67% rename from python/ray/dashboard/client/src/pages/dashboard/features/Host.tsx rename to python/ray/dashboard/client/src/pages/dashboard/node-info/features/Host.tsx index 71f87a97d..5529ee378 100644 --- a/python/ray/dashboard/client/src/pages/dashboard/features/Host.tsx +++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Host.tsx @@ -1,5 +1,16 @@ import React from "react"; -import { NodeFeatureComponent, WorkerFeatureComponent } from "./types"; +import { + ClusterFeatureComponent, + NodeFeatureComponent, + WorkerFeatureComponent +} from "./types"; + +export const ClusterHost: ClusterFeatureComponent = ({ nodes }) => ( + + Totals ({nodes.length.toLocaleString()}{" "} + {nodes.length === 1 ? "host" : "hosts"}) + +); export const NodeHost: NodeFeatureComponent = ({ node }) => ( diff --git a/python/ray/dashboard/client/src/pages/dashboard/features/Logs.tsx b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Logs.tsx similarity index 62% rename from python/ray/dashboard/client/src/pages/dashboard/features/Logs.tsx rename to python/ray/dashboard/client/src/pages/dashboard/node-info/features/Logs.tsx index 25039da17..06072cb70 100644 --- a/python/ray/dashboard/client/src/pages/dashboard/features/Logs.tsx +++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Logs.tsx @@ -2,7 +2,36 @@ import Link from "@material-ui/core/Link"; import Typography from "@material-ui/core/Typography"; import React from "react"; import { Link as RouterLink } from "react-router-dom"; -import { NodeFeatureComponent, WorkerFeatureComponent } from "./types"; +import { + ClusterFeatureComponent, + NodeFeatureComponent, + WorkerFeatureComponent +} from "./types"; + +export const makeClusterLogs = (logCounts: { + [ip: string]: { + perWorker: { + [pid: string]: number; + }; + total: number; + }; +}): ClusterFeatureComponent => ({ nodes }) => { + let totalLogCount = 0; + for (const node of nodes) { + if (node.ip in logCounts) { + totalLogCount += logCounts[node.ip].total; + } + } + return totalLogCount === 0 ? ( + + No logs + + ) : ( + + {totalLogCount.toLocaleString()} {totalLogCount === 1 ? "line" : "lines"} + + ); +}; export const makeNodeLogs = (logCounts: { perWorker: { [pid: string]: number }; diff --git a/python/ray/dashboard/client/src/pages/dashboard/node-info/features/RAM.tsx b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/RAM.tsx new file mode 100644 index 000000000..09f055d09 --- /dev/null +++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/RAM.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import { formatByteAmount, formatUsage } from "../../../../common/formatUtils"; +import UsageBar from "../../../../common/UsageBar"; +import { + ClusterFeatureComponent, + NodeFeatureComponent, + WorkerFeatureComponent +} from "./types"; + +export const ClusterRAM: ClusterFeatureComponent = ({ nodes }) => { + let used = 0; + let total = 0; + for (const node of nodes) { + used += node.mem[0] - node.mem[1]; + total += node.mem[0]; + } + return ( + + ); +}; + +export const NodeRAM: NodeFeatureComponent = ({ node }) => ( + +); + +export const WorkerRAM: WorkerFeatureComponent = ({ node, worker }) => ( + +); diff --git a/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Received.tsx b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Received.tsx new file mode 100644 index 000000000..eea1e0821 --- /dev/null +++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Received.tsx @@ -0,0 +1,30 @@ +import Typography from "@material-ui/core/Typography"; +import React from "react"; +import { formatByteAmount } from "../../../../common/formatUtils"; +import { + ClusterFeatureComponent, + NodeFeatureComponent, + WorkerFeatureComponent +} from "./types"; + +export const ClusterReceived: ClusterFeatureComponent = ({ nodes }) => { + let totalReceived = 0; + for (const node of nodes) { + totalReceived += node.net[1]; + } + return ( + + {formatByteAmount(totalReceived, "mebibyte")}/s + + ); +}; + +export const NodeReceived: NodeFeatureComponent = ({ node }) => ( + {formatByteAmount(node.net[1], "mebibyte")}/s +); + +export const WorkerReceived: WorkerFeatureComponent = () => ( + + N/A + +); diff --git a/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Sent.tsx b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Sent.tsx new file mode 100644 index 000000000..c35e0a88f --- /dev/null +++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Sent.tsx @@ -0,0 +1,28 @@ +import Typography from "@material-ui/core/Typography"; +import React from "react"; +import { formatByteAmount } from "../../../../common/formatUtils"; +import { + ClusterFeatureComponent, + NodeFeatureComponent, + WorkerFeatureComponent +} from "./types"; + +export const ClusterSent: ClusterFeatureComponent = ({ nodes }) => { + let totalSent = 0; + for (const node of nodes) { + totalSent += node.net[0]; + } + return ( + {formatByteAmount(totalSent, "mebibyte")}/s + ); +}; + +export const NodeSent: NodeFeatureComponent = ({ node }) => ( + {formatByteAmount(node.net[0], "mebibyte")}/s +); + +export const WorkerSent: WorkerFeatureComponent = () => ( + + N/A + +); diff --git a/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Uptime.tsx b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Uptime.tsx new file mode 100644 index 000000000..74d8b5dd6 --- /dev/null +++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Uptime.tsx @@ -0,0 +1,26 @@ +import Typography from "@material-ui/core/Typography"; +import React from "react"; +import { formatDuration } from "../../../../common/formatUtils"; +import { + ClusterFeatureComponent, + NodeFeatureComponent, + WorkerFeatureComponent +} from "./types"; + +const getUptime = (bootTime: number) => Date.now() / 1000 - bootTime; + +export const ClusterUptime: ClusterFeatureComponent = ({ nodes }) => ( + + N/A + +); + +export const NodeUptime: NodeFeatureComponent = ({ node }) => ( + {formatDuration(getUptime(node.boot_time))} +); + +export const WorkerUptime: WorkerFeatureComponent = ({ worker }) => ( + + {formatDuration(getUptime(worker.create_time))} + +); diff --git a/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Workers.tsx b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Workers.tsx new file mode 100644 index 000000000..48aa125a0 --- /dev/null +++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Workers.tsx @@ -0,0 +1,40 @@ +import React from "react"; +import { + ClusterFeatureComponent, + NodeFeatureComponent, + WorkerFeatureComponent +} from "./types"; + +export const ClusterWorkers: ClusterFeatureComponent = ({ nodes }) => { + let totalWorkers = 0; + let totalCpus = 0; + for (const node of nodes) { + totalWorkers += node.workers.length; + totalCpus += node.cpus[0]; + } + return ( + + {totalWorkers.toLocaleString()}{" "} + {totalWorkers === 1 ? "worker" : "workers"} / {totalCpus.toLocaleString()}{" "} + {totalCpus === 1 ? "core" : "cores"} + + ); +}; + +export const NodeWorkers: NodeFeatureComponent = ({ node }) => { + const workers = node.workers.length; + const cpus = node.cpus[0]; + return ( + + {workers.toLocaleString()} {workers === 1 ? "worker" : "workers"} /{" "} + {cpus.toLocaleString()} {cpus === 1 ? "core" : "cores"} + + ); +}; + +// Ray worker process titles have one of the following forms: `ray::IDLE`, +// `ray::function()`, `ray::Class`, or `ray::Class.method()`. We extract the +// second portion here for display in the "Workers" column. +export const WorkerWorkers: WorkerFeatureComponent = ({ worker }) => ( + {worker.cmdline[0].split("::", 2)[1]} +); diff --git a/python/ray/dashboard/client/src/pages/dashboard/features/types.tsx b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/types.tsx similarity index 70% rename from python/ray/dashboard/client/src/pages/dashboard/features/types.tsx rename to python/ray/dashboard/client/src/pages/dashboard/node-info/features/types.tsx index f3320e0c2..55460d368 100644 --- a/python/ray/dashboard/client/src/pages/dashboard/features/types.tsx +++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/types.tsx @@ -1,13 +1,17 @@ import React from "react"; -import { NodeInfoResponse } from "../../../api"; +import { NodeInfoResponse } from "../../../../api"; type ArrayType = T extends Array ? U : never; type Node = ArrayType; type Worker = ArrayType; +type ClusterFeatureData = { nodes: Node[] }; type NodeFeatureData = { node: Node }; type WorkerFeatureData = { node: Node; worker: Worker }; +export type ClusterFeatureComponent = ( + data: ClusterFeatureData +) => React.ReactElement; export type NodeFeatureComponent = ( data: NodeFeatureData ) => React.ReactElement; diff --git a/python/ray/dashboard/client/src/pages/dashboard/ray-config/RayConfig.tsx b/python/ray/dashboard/client/src/pages/dashboard/ray-config/RayConfig.tsx new file mode 100644 index 000000000..6a3419e4b --- /dev/null +++ b/python/ray/dashboard/client/src/pages/dashboard/ray-config/RayConfig.tsx @@ -0,0 +1,132 @@ +import { Theme } from "@material-ui/core/styles/createMuiTheme"; +import createStyles from "@material-ui/core/styles/createStyles"; +import withStyles, { WithStyles } from "@material-ui/core/styles/withStyles"; +import Table from "@material-ui/core/Table"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableHead from "@material-ui/core/TableHead"; +import TableRow from "@material-ui/core/TableRow"; +import Typography from "@material-ui/core/Typography"; +import classNames from "classnames"; +import React from "react"; +import { connect } from "react-redux"; +import { getRayConfig } from "../../../api"; +import { StoreState } from "../../../store"; +import { dashboardActions } from "../state"; + +const styles = (theme: Theme) => + createStyles({ + table: { + marginTop: theme.spacing(1), + width: "auto" + }, + cell: { + paddingTop: theme.spacing(1), + paddingBottom: theme.spacing(1), + paddingLeft: theme.spacing(3), + paddingRight: theme.spacing(3), + textAlign: "center", + "&:last-child": { + paddingRight: theme.spacing(3) + } + }, + key: { + color: theme.palette.text.secondary + } + }); + +const mapStateToProps = (state: StoreState) => ({ + rayConfig: state.dashboard.rayConfig +}); + +const mapDispatchToProps = dashboardActions; + +class RayConfig extends React.Component< + WithStyles & + ReturnType & + typeof mapDispatchToProps +> { + refreshRayConfig = async () => { + try { + const rayConfig = await getRayConfig(); + this.props.setRayConfig(rayConfig); + } catch (error) { + } finally { + setTimeout(this.refreshRayConfig, 10 * 1000); + } + }; + + async componentDidMount() { + await this.refreshRayConfig(); + } + + render() { + const { classes, rayConfig } = this.props; + + if (rayConfig === null) { + return null; + } + + const formattedRayConfig = [ + { + key: "Autoscaling mode", + value: rayConfig.autoscaling_mode + }, + { + key: "Head node type", + value: rayConfig.head_type + }, + { + key: "Worker node type", + value: rayConfig.worker_type + }, + { + key: "Min worker nodes", + value: rayConfig.min_workers + }, + { + key: "Initial worker nodes", + value: rayConfig.initial_workers + }, + { + key: "Max worker nodes", + value: rayConfig.max_workers + }, + { + key: "Idle timeout", + value: `${rayConfig.idle_timeout_minutes} ${ + rayConfig.idle_timeout_minutes === 1 ? "minute" : "minutes" + }` + } + ]; + + return ( +
+ Ray cluster configuration: + + + + Setting + Value + + + + {formattedRayConfig.map(({ key, value }, index) => ( + + + {key} + + {value} + + ))} + +
+
+ ); + } +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(withStyles(styles)(RayConfig)); diff --git a/python/ray/dashboard/client/src/pages/dashboard/state.ts b/python/ray/dashboard/client/src/pages/dashboard/state.ts index a31c327d6..d08ef1290 100644 --- a/python/ray/dashboard/client/src/pages/dashboard/state.ts +++ b/python/ray/dashboard/client/src/pages/dashboard/state.ts @@ -1,15 +1,17 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; -import { NodeInfoResponse } from "../../api"; +import { NodeInfoResponse, RayConfigResponse } from "../../api"; const name = "dashboard"; interface State { + rayConfig: RayConfigResponse | null; nodeInfo: NodeInfoResponse | null; lastUpdatedAt: number | null; error: string | null; } const initialState: State = { + rayConfig: null, nodeInfo: null, lastUpdatedAt: null, error: null @@ -19,11 +21,14 @@ const slice = createSlice({ name, initialState, reducers: { + setRayConfig: (state, action: PayloadAction) => { + state.rayConfig = action.payload; + }, setNodeInfo: (state, action: PayloadAction) => { state.nodeInfo = action.payload; state.lastUpdatedAt = Date.now(); }, - setError: (state, action: PayloadAction) => { + setError: (state, action: PayloadAction) => { state.error = action.payload; } } diff --git a/python/ray/dashboard/dashboard.py b/python/ray/dashboard/dashboard.py index 0c9e211ee..ba90ca4a4 100644 --- a/python/ray/dashboard/dashboard.py +++ b/python/ray/dashboard/dashboard.py @@ -21,8 +21,6 @@ import time import traceback import yaml -from pathlib import Path -from collections import Counter from collections import defaultdict from operator import itemgetter from typing import Dict @@ -113,8 +111,8 @@ class Dashboard(object): async def ray_config(_) -> aiohttp.web.Response: try: - with open( - Path("~/ray_bootstrap_config.yaml").expanduser()) as f: + config_path = os.path.expanduser("~/ray_bootstrap_config.yaml") + with open(config_path) as f: cfg = yaml.safe_load(f) except Exception: return await json_response(error="No config") @@ -213,51 +211,6 @@ class NodeStats(threading.Thread): super().__init__() - def calculate_totals(self) -> Dict: - total_boot_time = 0 - total_cpus = 0 - total_workers = 0 - total_load = [0.0, 0.0, 0.0] - total_storage_avail = 0 - total_storage_total = 0 - total_ram_avail = 0 - total_ram_total = 0 - total_sent = 0 - total_recv = 0 - - for v in self._node_stats.values(): - total_boot_time += v["boot_time"] - total_cpus += v["cpus"][0] - total_workers += len(v["workers"]) - total_load[0] += v["load_avg"][0][0] - total_load[1] += v["load_avg"][0][1] - total_load[2] += v["load_avg"][0][2] - total_storage_avail += v["disk"]["/"]["free"] - total_storage_total += v["disk"]["/"]["total"] - total_ram_avail += v["mem"][1] - total_ram_total += v["mem"][0] - total_sent += v["net"][0] - total_recv += v["net"][1] - - return { - "boot_time": total_boot_time, - "n_workers": total_workers, - "n_cores": total_cpus, - "m_avail": total_ram_avail, - "m_total": total_ram_total, - "d_avail": total_storage_avail, - "d_total": total_storage_total, - "load": total_load, - "n_sent": total_sent, - "n_recv": total_recv, - } - - def calculate_tasks(self) -> Counter: - return Counter( - (x["name"] - for y in (v["workers"] for v in self._node_stats.values()) - for x in y)) - def calculate_log_counts(self): return { ip: { @@ -296,8 +249,6 @@ class NodeStats(threading.Thread): (v for v in self._node_stats.values()), key=itemgetter("boot_time")) return { - "totals": self.calculate_totals(), - "tasks": self.calculate_tasks(), "clients": node_stats, "log_counts": self.calculate_log_counts(), "error_counts": self.calculate_error_counts(), diff --git a/python/ray/reporter.py b/python/ray/reporter.py index 3c3f5848f..b04e4c181 100644 --- a/python/ray/reporter.py +++ b/python/ray/reporter.py @@ -112,8 +112,8 @@ class Reporter(object): def get_workers(): return [ x.as_dict(attrs=[ - "pid", "create_time", "cpu_percent", "cpu_times", "name", - "cmdline", "memory_info", "memory_full_info" + "pid", "create_time", "cpu_percent", "cpu_times", "cmdline", + "memory_info", "memory_full_info" ]) for x in psutil.process_iter(attrs=["cmdline"]) if is_worker(x.info["cmdline"]) ]