Skip to content

Commit

Permalink
[Dashboard] Add support for new backend to existing front-end (ray-pr…
Browse files Browse the repository at this point in the history
…oject#11013)

* Trying to commit on top of old code again

* address comment

Co-authored-by: Max Fitton <[email protected]>
  • Loading branch information
mfitton and Max Fitton committed Oct 2, 2020
1 parent 6ed8459 commit 5a42ed1
Show file tree
Hide file tree
Showing 30 changed files with 609 additions and 688 deletions.
406 changes: 223 additions & 183 deletions dashboard/client/src/api.ts

Large diffs are not rendered by default.

154 changes: 78 additions & 76 deletions dashboard/client/src/pages/dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import {
createStyles,
makeStyles,
Tab,
Tabs,
Theme,
Typography,
WithStyles,
withStyles,
} from "@material-ui/core";
import React from "react";
import { connect } from "react-redux";
import { getNodeInfo, getRayletInfo, getTuneAvailability } from "../../api";
import React, { useCallback, useEffect, useRef } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getActorGroups, getNodeInfo, getTuneAvailability } from "../../api";
import { StoreState } from "../../store";
import LastUpdated from "./LastUpdated";
import LogicalView from "./logical-view/LogicalView";
Expand All @@ -19,7 +18,14 @@ import RayConfig from "./ray-config/RayConfig";
import { dashboardActions } from "./state";
import Tune from "./tune/Tune";

const styles = (theme: Theme) =>
const {
setNodeInfo,
setTuneAvailability,
setActorGroups,
setError,
setTab,
} = dashboardActions;
const useDashboardStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
backgroundColor: theme.palette.background.paper,
Expand All @@ -33,89 +39,85 @@ const styles = (theme: Theme) =>
borderBottomStyle: "solid",
borderBottomWidth: 1,
},
});
}),
);

const mapStateToProps = (state: StoreState) => ({
tab: state.dashboard.tab,
tuneAvailability: state.dashboard.tuneAvailability,
});
const tabSelector = (state: StoreState) => state.dashboard.tab;
const tuneAvailabilitySelector = (state: StoreState) =>
state.dashboard.tuneAvailability;

const mapDispatchToProps = dashboardActions;
const allTabs = [
{ label: "Machine view", component: NodeInfo },
{ label: "Logical view", component: LogicalView },
{ label: "Memory", component: MemoryInfo },
{ label: "Ray config", component: RayConfig },
{ label: "Tune", component: Tune },
];

class Dashboard extends React.Component<
WithStyles<typeof styles> &
ReturnType<typeof mapStateToProps> &
typeof mapDispatchToProps
> {
timeoutId = 0;
tabs = [
{ label: "Machine view", component: NodeInfo },
{ label: "Logical view", component: LogicalView },
{ label: "Memory", component: MemoryInfo },
{ label: "Ray config", component: RayConfig },
{ label: "Tune", component: Tune },
];
const Dashboard: React.FC = () => {
const dispatch = useDispatch();
const tuneAvailability = useSelector(tuneAvailabilitySelector);
const tab = useSelector(tabSelector);
const classes = useDashboardStyles();

refreshInfo = async () => {
// Polling Function
const refreshInfo = useCallback(async () => {
try {
const [nodeInfo, rayletInfo, tuneAvailability] = await Promise.all([
const [nodeInfo, tuneAvailability, actorGroups] = await Promise.all([
getNodeInfo(),
getRayletInfo(),
getTuneAvailability(),
getActorGroups(),
]);
this.props.setNodeAndRayletInfo({ nodeInfo, rayletInfo });
this.props.setTuneAvailability(tuneAvailability);
this.props.setError(null);
dispatch(setNodeInfo({ nodeInfo }));
dispatch(setTuneAvailability(tuneAvailability));
dispatch(setActorGroups(actorGroups));
dispatch(setError(null));
} catch (error) {
this.props.setError(error.toString());
} finally {
this.timeoutId = window.setTimeout(this.refreshInfo, 1000);
dispatch(setError(error.toString()));
}
};
}, [dispatch]);

async componentDidMount() {
await this.refreshInfo();
}

componentWillUnmount() {
clearTimeout(this.timeoutId);
}

handleTabChange = async (event: React.ChangeEvent<{}>, value: number) =>
this.props.setTab(value);
// Run the poller
const intervalId = useRef<any>(null);
useEffect(() => {
if (intervalId.current === null) {
refreshInfo();
intervalId.current = setInterval(refreshInfo, 1000);
}
const cleanup = () => {
clearInterval(intervalId.current);
};
return cleanup;
}, [refreshInfo]);

render() {
const { classes, tab, tuneAvailability } = this.props;
const tabs = this.tabs.slice();
const handleTabChange = (_: any, value: number) => dispatch(setTab(value));

// if Tune information is not available, remove Tune tab from the dashboard
if (tuneAvailability === null || !tuneAvailability.available) {
tabs.splice(4);
}
const tabs = allTabs.slice();

const SelectedComponent = tabs[tab].component;
return (
<div className={classes.root}>
<Typography variant="h5">Ray Dashboard</Typography>
<Tabs
className={classes.tabs}
indicatorColor="primary"
onChange={this.handleTabChange}
textColor="primary"
value={tab}
>
{tabs.map(({ label }) => (
<Tab key={label} label={label} />
))}
</Tabs>
<SelectedComponent />
<LastUpdated />
</div>
);
// if Tune information is not available, remove Tune tab from the dashboard
if (tuneAvailability === null || !tuneAvailability.available) {
tabs.splice(4);
}
}

export default connect(
mapStateToProps,
mapDispatchToProps,
)(withStyles(styles)(Dashboard));
const SelectedComponent = tabs[tab].component;
return (
<div className={classes.root}>
<Typography variant="h5">Ray Dashboard</Typography>
<Tabs
className={classes.tabs}
indicatorColor="primary"
onChange={handleTabChange}
textColor="primary"
value={tab}
>
{tabs.map(({ label }) => (
<Tab key={label} label={label} />
))}
</Tabs>
<SelectedComponent />
<LastUpdated />
</div>
);
};

export default Dashboard;
17 changes: 9 additions & 8 deletions dashboard/client/src/pages/dashboard/logical-view/Actor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ const Actor: React.FC<ActorProps> = ({ actor }) => {
{
label: "Resources",
value:
actor.usedResources &&
Object.entries(actor.usedResources).length > 0 &&
Object.entries(actor.usedResources)
.sort((a, b) => a[0].localeCompare(b[0]))
Expand All @@ -121,20 +122,20 @@ const Actor: React.FC<ActorProps> = ({ actor }) => {
},
{
label: "Number of pending tasks",
value: actor.taskQueueLength.toLocaleString(),
value: actor.taskQueueLength?.toLocaleString() ?? "0",
tooltip:
"The number of tasks that are currently pending to execute on this actor. If this number " +
"remains consistently high, it may indicate that this actor is a bottleneck in your application.",
},
{
label: "Number of executed tasks",
value: actor.numExecutedTasks.toLocaleString(),
value: actor.numExecutedTasks?.toLocaleString() ?? "0",
tooltip:
"The number of tasks this actor has executed throughout its lifetimes.",
},
{
label: "Number of ObjectRefs in scope",
value: actor.numObjectRefsInScope.toLocaleString(),
value: actor.numObjectRefsInScope?.toLocaleString() ?? "0",
tooltip:
"The number of ObjectRefs that this actor is keeping in scope via its internal state. " +
"This does not imply that the objects are in active use or colocated on the node with the actor " +
Expand All @@ -143,14 +144,14 @@ const Actor: React.FC<ActorProps> = ({ actor }) => {
},
{
label: "Number of local objects",
value: actor.numLocalObjects.toLocaleString(),
value: actor.numLocalObjects?.toLocaleString() ?? "0",
tooltip:
"The number of small objects that this actor has stored in its local in-process memory store. This can be useful for " +
`debugging memory leaks. See the docs at ${memoryDebuggingDocLink} for more information`,
},
{
label: "Object store memory used (MiB)",
value: actor.usedObjectStoreMemory.toLocaleString(),
value: actor.usedObjectStoreMemory?.toLocaleString() ?? "0",
tooltip:
"The total amount of memory that this actor is occupying in the Ray object store. " +
"If this number is increasing without bounds, you might have a memory leak. See " +
Expand Down Expand Up @@ -263,18 +264,18 @@ const Actor: React.FC<ActorProps> = ({ actor }) => {
</React.Fragment>
) : actor.state === ActorState.Infeasible ? (
<span className={classes.infeasible}>
{actor.actorTitle} cannot be created because the Ray cluster cannot
{actor.actorClass} cannot be created because the Ray cluster cannot
satisfy its resource requirements.
</span>
) : (
<span className={classes.pendingResources}>
{actor.actorTitle} is pending until resources are available.
{actor.actorClass} is pending until resources are available.
</span>
)}
</Typography>
<ActorDetailsPane
actorDetails={information}
actorTitle={actor.actorTitle ?? ""}
actorClass={actor.actorClass}
actorState={actor.state}
/>
{isFullActorInfo(actor) && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import LabeledDatum from "../../../common/LabeledDatum";
import ActorStateRepr from "./ActorStateRepr";

type ActorDetailsPaneProps = {
actorTitle: string;
actorClass: string;
actorState: ActorState;
actorDetails: {
label: string;
Expand All @@ -31,15 +31,15 @@ const useStyles = makeStyles((theme: Theme) => ({
}));

const ActorDetailsPane: React.FC<ActorDetailsPaneProps> = ({
actorTitle,
actorDetails,
actorClass,
actorState,
}) => {
const classes = useStyles();
return (
<React.Fragment>
<div className={classes.actorTitleWrapper}>
<div>{actorTitle}</div>
<div>{actorClass}</div>
<ActorStateRepr state={actorState} />
</div>
<Divider className={classes.divider} />
Expand Down
23 changes: 15 additions & 8 deletions dashboard/client/src/pages/dashboard/logical-view/LogicalView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,32 @@ const actorClassMatchesSearch = (
return actorClass.toLowerCase().search(loweredNameFilter) !== -1;
};

const rayletInfoSelector = (state: StoreState) => state.dashboard.rayletInfo;
const actorGroupsSelector = (state: StoreState) => state.dashboard.actorGroups;

const LogicalView: React.FC = () => {
const [nameFilter, setNameFilter] = useState("");
const [debouncedNameFilter] = useDebounce(nameFilter, 500);
const classes = useLogicalViewStyles();
const rayletInfo = useSelector(rayletInfoSelector);
if (rayletInfo === null || !rayletInfo.actorGroups) {
const actorGroups = useSelector(actorGroupsSelector);
if (!actorGroups) {
return <Typography color="textSecondary">Loading...</Typography>;
}
const actorGroups =
if (Object.keys(actorGroups).length === 0) {
return (
<Typography color="textSecondary">
Finished loading, but have found no actors yet.
</Typography>
);
}
const filteredGroups =
debouncedNameFilter === ""
? Object.entries(rayletInfo.actorGroups)
: Object.entries(rayletInfo.actorGroups).filter(([key, _]) =>
? Object.entries(actorGroups)
: Object.entries(actorGroups).filter(([key, _]) =>
actorClassMatchesSearch(key, debouncedNameFilter),
);
return (
<Box className={classes.container}>
{actorGroups.length === 0 ? (
{filteredGroups.length === 0 ? (
<Typography color="textSecondary">No actors found.</Typography>
) : (
<React.Fragment>
Expand All @@ -65,7 +72,7 @@ const LogicalView: React.FC = () => {
Search for an actor by name
</FormHelperText>
</FormControl>
<ActorClassGroups actorGroups={Object.fromEntries(actorGroups)} />
<ActorClassGroups actorGroups={Object.fromEntries(filteredGroups)} />
</React.Fragment>
)}
</Box>
Expand Down
Loading

0 comments on commit 5a42ed1

Please sign in to comment.