[Dashboard] Memory View Group by Stack Trace and UI Overhaul (#10227)

This commit is contained in:
Max Fitton
2020-08-24 12:54:42 -07:00
committed by GitHub
parent c8c4832794
commit 832f5cdccb
14 changed files with 463 additions and 414 deletions
+7 -7
View File
@@ -355,7 +355,7 @@ export type MemoryTableSummary = {
total_object_size: number;
total_pinned_in_memory: number;
total_used_by_pending_task: number;
} | null;
};
export type MemoryTableEntry = {
node_ip_address: string;
@@ -384,12 +384,12 @@ export type MemoryTableResponse = {
// This doesn't return anything.
export type StopMemoryTableResponse = {};
export const getMemoryTable = (shouldObtainMemoryTable: boolean) => {
if (shouldObtainMemoryTable) {
return get<MemoryTableResponse>("/api/memory_table", {});
} else {
return null;
}
export type MemoryGroupByKey = "node" | "stack_trace" | "";
export const getMemoryTable = async (groupByKey: MemoryGroupByKey) => {
return get<MemoryTableResponse>("/api/memory_table", {
group_by: groupByKey,
});
};
export const stopMemoryTableCollection = () =>
@@ -0,0 +1,19 @@
import ExpandLessIcon from "@material-ui/icons/ExpandLess";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import React from "react";
type MinimizerProps = {
onClick: React.MouseEventHandler;
};
type ExpanderProps = {
onClick: React.MouseEventHandler;
};
export const Minimizer: React.FC<MinimizerProps> = ({ onClick }) => (
<ExpandLessIcon onClick={onClick} />
);
export const Expander: React.FC<ExpanderProps> = ({ onClick }) => (
<ExpandMoreIcon onClick={onClick} />
);
@@ -0,0 +1,39 @@
import { Grid, makeStyles, Tooltip } from "@material-ui/core";
import React from "react";
type LabeledDatumProps = {
label: string;
datum: any;
tooltip?: string;
};
const useLabeledDatumStyles = makeStyles({
tooltipLabel: {
textDecorationLine: "underline",
textDecorationColor: "#a6c3e3",
textDecorationThickness: "1px",
textDecorationStyle: "dotted",
cursor: "help",
},
});
const LabeledDatum: React.FC<LabeledDatumProps> = ({
label,
datum,
tooltip,
}) => {
const classes = useLabeledDatumStyles();
const innerHtml = (
<Grid container item xs={6}>
<Grid item xs={6}>
<span className={tooltip && classes.tooltipLabel}>{label}</span>
</Grid>
<Grid item xs={6}>
<span>{datum}</span>
</Grid>
</Grid>
);
return tooltip ? <Tooltip title={tooltip}>{innerHtml}</Tooltip> : innerHtml;
};
export default LabeledDatum;
@@ -1,3 +1,5 @@
import { useEffect, useRef } from "react";
export const getWeightedAverage = (
input: {
weight: number;
@@ -24,3 +26,24 @@ export const filterObj = (obj: Object, filterFn: any) =>
export const mapObj = (obj: Object, filterFn: any) =>
Object.fromEntries(Object.entries(obj).map(filterFn));
export const useInterval = (callback: Function, delayMs: number) => {
const savedCallback = useRef<any>();
const intervalId = useRef<any>();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
const tick = () => savedCallback?.current();
intervalId.current = setInterval(tick, delayMs);
savedCallback.current();
return () => {
if (intervalId.current) {
clearInterval(intervalId.current);
}
};
}, [callback, delayMs]);
return intervalId.current
? () => clearInterval(intervalId.current)
: () => null;
};
@@ -9,13 +9,7 @@ import {
} from "@material-ui/core";
import React from "react";
import { connect } from "react-redux";
import {
getMemoryTable,
getNodeInfo,
getRayletInfo,
getTuneAvailability,
stopMemoryTableCollection,
} from "../../api";
import { getNodeInfo, getRayletInfo, getTuneAvailability } from "../../api";
import { StoreState } from "../../store";
import LastUpdated from "./LastUpdated";
import LogicalView from "./logical-view/LogicalView";
@@ -44,7 +38,6 @@ const styles = (theme: Theme) =>
const mapStateToProps = (state: StoreState) => ({
tab: state.dashboard.tab,
tuneAvailability: state.dashboard.tuneAvailability,
shouldObtainMemoryTable: state.dashboard.shouldObtainMemoryTable,
});
const mapDispatchToProps = dashboardActions;
@@ -64,25 +57,15 @@ class Dashboard extends React.Component<
];
refreshInfo = async () => {
const { shouldObtainMemoryTable } = this.props;
try {
const [
nodeInfo,
rayletInfo,
memoryTable,
tuneAvailability,
] = await Promise.all([
const [nodeInfo, rayletInfo, tuneAvailability] = await Promise.all([
getNodeInfo(),
getRayletInfo(),
getMemoryTable(shouldObtainMemoryTable),
getTuneAvailability(),
]);
this.props.setNodeAndRayletInfo({ nodeInfo, rayletInfo });
this.props.setTuneAvailability(tuneAvailability);
this.props.setError(null);
if (shouldObtainMemoryTable) {
this.props.setMemoryTable(memoryTable);
}
} catch (error) {
this.props.setError(error.toString());
} finally {
@@ -98,15 +81,8 @@ class Dashboard extends React.Component<
clearTimeout(this.timeoutId);
}
handleTabChange = async (event: React.ChangeEvent<{}>, value: number) => {
handleTabChange = async (event: React.ChangeEvent<{}>, value: number) =>
this.props.setTab(value);
if (this.tabs[value].label === "Memory") {
this.props.setShouldObtainMemoryTable(true);
} else {
this.props.setShouldObtainMemoryTable(false);
await stopMemoryTableCollection();
}
};
render() {
const { classes, tab, tuneAvailability } = this.props;
@@ -4,45 +4,10 @@ import {
Grid,
makeStyles,
Theme,
Tooltip,
} from "@material-ui/core";
import React from "react";
import { ActorState, InvalidStateType } from "../../../api";
type LabeledDatumProps = {
label: string;
datum: any;
tooltip?: string;
};
const useLabeledDatumStyles = makeStyles({
label: {
textDecorationLine: "underline",
textDecorationColor: "#a6c3e3",
textDecorationThickness: "1px",
textDecorationStyle: "dotted",
cursor: "help",
},
});
const LabeledDatum: React.FC<LabeledDatumProps> = ({
label,
datum,
tooltip,
}) => {
const classes = useLabeledDatumStyles();
const innerHtml = (
<Grid container item xs={6}>
<Grid item xs={6}>
<span className={classes.label}>{label}</span>
</Grid>
<Grid item xs={6}>
<span>{datum}</span>
</Grid>
</Grid>
);
return tooltip ? <Tooltip title={tooltip}>{innerHtml}</Tooltip> : innerHtml;
};
import LabeledDatum from "../../../common/LabeledDatum";
type ActorStateReprProps = {
state: ActorState;
@@ -1,145 +1,68 @@
import {
Box,
Button,
Checkbox,
createStyles,
FormControlLabel,
FormControl,
InputLabel,
makeStyles,
Table,
TableBody,
MenuItem,
Select,
Theme,
Typography,
} from "@material-ui/core";
import PauseIcon from "@material-ui/icons/Pause";
import PlayArrowIcon from "@material-ui/icons/PlayArrow";
import React, { useState } from "react";
import SubdirectoryArrowRightIcon from "@material-ui/icons/SubdirectoryArrowRight";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
MemoryTableEntry,
MemoryTableGroups,
getMemoryTable,
MemoryGroupByKey,
MemoryTableResponse,
stopMemoryTableCollection,
} from "../../../api";
import SortableTableHead, {
HeaderInfo,
} from "../../../common/SortableTableHead";
import { getComparator, Order, stableSort } from "../../../common/tableUtils";
import { StoreState } from "../../../store";
import { dashboardActions } from "../state";
import ExpanderRow from "./ExpanderRow";
import MemoryRowGroup from "./MemoryRowGroup";
import { MemoryTableRow } from "./MemoryTableRow";
const DEFAULT_ENTRIES_PER_GROUP = 10;
const DEFAULT_UNGROUPED_ENTRIES = 25;
type GroupedMemoryRowsProps = {
memoryTableGroups: MemoryTableGroups;
order: Order;
orderBy: keyof MemoryTableEntry | null;
const groupTitle = (groupKey: string, groupBy: MemoryGroupByKey) => {
if (groupBy === "node") {
return <Typography variant="h6">{`Node ${groupKey}`}</Typography>;
}
if (groupBy === "stack_trace") {
return <PyStackTrace stackTrace={groupKey} />;
}
if (groupBy === "") {
return <Typography variant="h6">All entries</Typography>;
}
return <Typography variant="h6">Unknown Group</Typography>;
};
const GroupedMemoryRows: React.FC<GroupedMemoryRowsProps> = ({
memoryTableGroups,
order,
orderBy,
}) => {
const comparator = orderBy && getComparator(order, orderBy);
return (
<React.Fragment>
{Object.entries(memoryTableGroups).map(([groupKey, group]) => {
const sortedEntries = comparator
? stableSort(group.entries, comparator)
: group.entries;
return (
<MemoryRowGroup
groupKey={groupKey}
summary={group.summary}
entries={sortedEntries}
initialExpanded={true}
initialVisibleEntries={DEFAULT_ENTRIES_PER_GROUP}
/>
);
})}
</React.Fragment>
);
const PyStackTrace: React.FC<{ stackTrace: string }> = ({ stackTrace }) => {
const stackFrames = stackTrace.split(" | ");
const renderedFrames = stackFrames.map((frame, i) => (
<Typography
variant={i === 0 ? "h6" : "subtitle2"}
style={{ marginLeft: `${i}em` }}
>
{i !== 0 && <SubdirectoryArrowRightIcon />}
{frame}
</Typography>
));
return <Box>{renderedFrames}</Box>;
};
type UngroupedMemoryRowsProps = {
memoryTableGroups: MemoryTableGroups;
order: Order;
orderBy: memoryColumnId | null;
};
const UngroupedMemoryRows: React.FC<UngroupedMemoryRowsProps> = ({
memoryTableGroups,
order,
orderBy,
}) => {
const [visibleEntries, setVisibleEntries] = useState(
DEFAULT_UNGROUPED_ENTRIES,
);
const onExpand = () => setVisibleEntries(visibleEntries + 10);
const allEntries = Object.values(memoryTableGroups).reduce(
(allEntries: Array<MemoryTableEntry>, memoryTableGroup) => {
const groupEntries = memoryTableGroup.entries;
return allEntries.concat(groupEntries);
},
[],
);
const sortedEntries =
orderBy === null
? allEntries
: stableSort(allEntries, getComparator(order, orderBy));
return (
<React.Fragment>
{" "}
{sortedEntries.slice(0, visibleEntries).map((memoryTableEntry, index) => (
<MemoryTableRow
memoryTableEntry={memoryTableEntry}
key={index.toString()}
/>
))}
<ExpanderRow onExpand={onExpand} />
</React.Fragment>
);
};
type memoryColumnId =
| "node_ip_address"
| "pid"
| "type"
| "object_ref"
| "object_size"
| "reference_type"
| "call_site";
const memoryHeaderInfo: HeaderInfo<memoryColumnId>[] = [
{ id: "node_ip_address", label: "IP Address", numeric: true, sortable: true },
{ id: "pid", label: "pid", numeric: true, sortable: true },
{ id: "type", label: "Type", numeric: false, sortable: true },
{ id: "object_ref", label: "Object Ref", numeric: false, sortable: true },
{
id: "object_size",
label: "Object Size (B)",
numeric: true,
sortable: true,
},
{
id: "reference_type",
label: "Reference Type",
numeric: false,
sortable: true,
},
{ id: "call_site", label: "Call Site", numeric: false, sortable: true },
];
const MEMORY_POLLING_INTERVAL_MS = 4000;
const useMemoryInfoStyles = makeStyles((theme: Theme) =>
createStyles({
table: {
marginTop: theme.spacing(1),
},
cell: {
pauseButton: {
margin: theme.spacing(1),
padding: theme.spacing(1),
textAlign: "center",
float: "right",
},
select: {
minWidth: "7em",
},
}),
);
@@ -150,84 +73,105 @@ const memoryInfoSelector = (state: StoreState) => ({
shouldObtainMemoryTable: state.dashboard.shouldObtainMemoryTable,
});
const MemoryInfo: React.FC<{}> = () => {
const { memoryTable, shouldObtainMemoryTable } = useSelector(
memoryInfoSelector,
);
const dispatch = useDispatch();
const toggleMemoryCollection = async () => {
dispatch(
dashboardActions.setShouldObtainMemoryTable(!shouldObtainMemoryTable),
);
if (shouldObtainMemoryTable) {
await stopMemoryTableCollection();
}
const fetchMemoryTable = (
groupByKey: MemoryGroupByKey,
setResults: (mtr: MemoryTableResponse) => void,
) => {
return async () => {
const resp = await getMemoryTable(groupByKey);
setResults(resp);
};
};
const MemoryInfo: React.FC<{}> = () => {
const { memoryTable } = useSelector(memoryInfoSelector);
const dispatch = useDispatch();
const [paused, setPaused] = useState(false);
const pauseButtonIcon = paused ? <PlayArrowIcon /> : <PauseIcon />;
const pauseButtonIcon = shouldObtainMemoryTable ? (
<PauseIcon />
) : (
<PlayArrowIcon />
);
const classes = useMemoryInfoStyles();
const [isGrouped, setIsGrouped] = useState(true);
const [order, setOrder] = React.useState<Order>("asc");
const toggleOrder = () => setOrder(order === "asc" ? "desc" : "asc");
const [orderBy, setOrderBy] = React.useState<memoryColumnId | null>(null);
const [groupBy, setGroupBy] = useState<MemoryGroupByKey>("node");
// Set up polling memory data
const fetchData = useCallback(
fetchMemoryTable(groupBy, (resp) =>
dispatch(dashboardActions.setMemoryTable(resp)),
),
[groupBy],
);
const intervalId = useRef<any>(null);
useEffect(() => {
if (!intervalId.current && !paused) {
fetchData();
intervalId.current = setInterval(fetchData, MEMORY_POLLING_INTERVAL_MS);
}
const cleanup = () => {
if (intervalId.current) {
clearInterval(intervalId.current);
intervalId.current = null;
}
};
return cleanup;
}, [paused, fetchData]);
if (!memoryTable) {
return (
<Typography variant="h5" align="center">
Loading memory information
</Typography>
);
}
const children = Object.entries(memoryTable.group)
.sort(([key1], [key2]) => (key1 < key2 ? -1 : 1))
.map(([groupKey, memoryGroup]) => (
<MemoryRowGroup
key={groupKey}
groupKey={groupKey}
groupTitle={groupTitle(groupKey, groupBy)}
entries={memoryGroup.entries}
summary={memoryGroup.summary}
initialExpanded={false}
initialVisibleEntries={10}
/>
));
return (
<React.Fragment>
{memoryTable !== null ? (
<React.Fragment>
<Button color="primary" onClick={toggleMemoryCollection}>
{pauseButtonIcon}
{shouldObtainMemoryTable ? "Pause Collection" : "Resume Collection"}
</Button>
<FormControlLabel
control={
<Checkbox
checked={isGrouped}
onChange={() => setIsGrouped(!isGrouped)}
color="primary"
/>
}
label="Group by host"
/>
<Table className={classes.table}>
<SortableTableHead
orderBy={orderBy}
order={order}
onRequestSort={(_, property) => {
if (property === orderBy) {
toggleOrder();
} else {
setOrderBy(property);
setOrder("asc");
}
}}
headerInfo={memoryHeaderInfo}
firstColumnEmpty={false}
/>
<TableBody>
{isGrouped ? (
<GroupedMemoryRows
memoryTableGroups={memoryTable.group}
order={order}
orderBy={orderBy}
/>
) : (
<UngroupedMemoryRows
memoryTableGroups={memoryTable.group}
order={order}
orderBy={orderBy}
/>
)}
</TableBody>
</Table>
</React.Fragment>
) : (
<div>No Memory Table Information Provided</div>
)}
</React.Fragment>
<Box>
<FormControl>
<InputLabel shrink id="group-by-label">
Group by
</InputLabel>
<Select
labelId="group-by-label"
value={groupBy}
className={classes.select}
onChange={(e: any) => setGroupBy(e.target.value)}
color="primary"
displayEmpty
>
<MenuItem value="">
<em>None</em>
</MenuItem>
<MenuItem value={"node"}>Node IP Address</MenuItem>
<MenuItem value={"stack_trace"}>Stack Trace</MenuItem>
</Select>
</FormControl>
<Button
color="primary"
className={classes.pauseButton}
onClick={() => {
if (!paused) {
stopMemoryTableCollection();
}
setPaused(!paused);
}}
>
{pauseButtonIcon}
{paused ? "Resume Collection" : "Pause Collection"}
</Button>
{children}
</Box>
);
};
@@ -1,35 +1,37 @@
import { createStyles, makeStyles, TableRow, Theme } from "@material-ui/core";
import AddIcon from "@material-ui/icons/Add";
import RemoveIcon from "@material-ui/icons/Remove";
import React, { useState } from "react";
import { MemoryTableEntry, MemoryTableSummary } from "../../../api";
import {
ExpandableStyledTableCell,
StyledTableCell,
} from "../../../common/TableCell";
import ExpanderRow from "./ExpanderRow";
Box,
createStyles,
makeStyles,
Paper,
styled,
Theme,
} from "@material-ui/core";
import React, { ReactChild, useState } from "react";
import { MemoryTableEntry, MemoryTableSummary } from "../../../api";
import { Expander, Minimizer } from "../../../common/ExpandControls";
import MemorySummary from "./MemorySummary";
import { MemoryTableRow } from "./MemoryTableRow";
import MemoryTable from "./MemoryTable";
const CenteredBox = styled(Box)({
textAlign: "center",
});
const useMemoryRowGroupStyles = makeStyles((theme: Theme) =>
createStyles({
expandCollapseCell: {
cursor: "pointer",
},
expandCollapseIcon: {
color: theme.palette.text.secondary,
fontSize: "1.5em",
verticalAlign: "middle",
},
extraInfo: {
fontFamily: "SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace",
whiteSpace: "pre",
container: {
marginTop: theme.spacing(2),
marginBottom: theme.spacing(2),
paddingTop: theme.spacing(2),
paddingBottom: theme.spacing(1),
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(2),
},
}),
);
type MemoryRowGroupProps = {
groupKey: string;
groupTitle: ReactChild;
summary: MemoryTableSummary;
entries: MemoryTableEntry[];
initialExpanded: boolean;
@@ -38,6 +40,7 @@ type MemoryRowGroupProps = {
const MemoryRowGroup: React.FC<MemoryRowGroupProps> = ({
groupKey,
groupTitle,
entries,
summary,
initialExpanded,
@@ -45,53 +48,32 @@ const MemoryRowGroup: React.FC<MemoryRowGroupProps> = ({
}) => {
const classes = useMemoryRowGroupStyles();
const [expanded, setExpanded] = useState(initialExpanded);
const [visibleEntries, setVisibleEntries] = useState(initialVisibleEntries);
const [numVisibleEntries, setNumVisibleEntries] = useState(
initialVisibleEntries,
);
const toggleExpanded = () => setExpanded(!expanded);
const features = [
"node_ip_address",
"pid",
"type",
"object_ref",
"object_size",
"reference_type",
"call_site",
];
const showMoreEntries = () => setNumVisibleEntries(numVisibleEntries + 10);
const visibleEntries = entries.slice(0, numVisibleEntries);
return (
<React.Fragment>
<TableRow hover>
<ExpandableStyledTableCell onClick={toggleExpanded}>
{!expanded ? (
<AddIcon className={classes.expandCollapseIcon} />
) : (
<RemoveIcon className={classes.expandCollapseIcon} />
)}
</ExpandableStyledTableCell>
{features.map((feature, index) => (
<StyledTableCell key={index}>
{// TODO(sang): For now, it is always grouped by node_ip_address.
feature === "node_ip_address" ? groupKey : ""}
</StyledTableCell>
))}
</TableRow>
{expanded && (
<Paper key={groupKey} className={classes.container}>
{groupTitle}
<MemorySummary initialExpanded={false} memoryTableSummary={summary} />
{expanded ? (
<React.Fragment>
<MemorySummary initialExpanded={false} memoryTableSummary={summary} />
{entries.slice(0, visibleEntries).map((memoryTableEntry, index) => {
return (
<MemoryTableRow
memoryTableEntry={memoryTableEntry}
key={`${index}`}
/>
);
})}
<ExpanderRow
onExpand={() => setVisibleEntries(visibleEntries + 10)}
/>
<MemoryTable tableEntries={visibleEntries} />
<CenteredBox>
{entries.length > numVisibleEntries && (
<Expander onClick={showMoreEntries} />
)}
<Minimizer onClick={toggleExpanded} />
</CenteredBox>
</React.Fragment>
) : (
<CenteredBox>
<Expander onClick={toggleExpanded} />
</CenteredBox>
)}
</React.Fragment>
</Paper>
);
};
@@ -1,91 +1,59 @@
import {
createStyles,
TableCell,
TableRow,
Theme,
withStyles,
WithStyles,
} from "@material-ui/core";
import { createStyles, Grid, makeStyles, Theme } from "@material-ui/core";
import React from "react";
import { MemoryTableSummary } from "../../../api";
import { formatByteAmount } from "../../../common/formatUtils";
import LabeledDatum from "../../../common/LabeledDatum";
const styles = (theme: Theme) =>
const useMemorySummaryStyles = makeStyles((theme: Theme) =>
createStyles({
cell: {
padding: theme.spacing(1),
textAlign: "center",
"&:last-child": {
paddingRight: theme.spacing(1),
},
},
expandCollapseCell: {
cursor: "pointer",
},
expandCollapseIcon: {
color: theme.palette.text.secondary,
fontSize: "1.5em",
verticalAlign: "middle",
},
extraInfo: {
fontFamily: "SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace",
whiteSpace: "pre",
container: {
padding: theme.spacing(1),
margin: theme.spacing(1),
},
});
}),
);
type Props = {
type MemorySummaryProps = {
memoryTableSummary: MemoryTableSummary;
initialExpanded: boolean;
};
type State = {
expanded: boolean;
const MemorySummary: React.FC<MemorySummaryProps> = ({
memoryTableSummary,
}) => {
const classes = useMemorySummaryStyles();
const memoryData = [
[
"Total Local Reference Count",
`${memoryTableSummary.total_local_ref_count}`,
],
["Pinned in Memory Count", `${memoryTableSummary.total_pinned_in_memory}`],
[
"Total Used by Pending Tasks Count",
`${memoryTableSummary.total_used_by_pending_task}`,
],
[
"Total Captured in Objects Count",
`${memoryTableSummary.total_captured_in_objects}`,
],
[
"Total Memory Used by Objects",
`${formatByteAmount(memoryTableSummary.total_object_size, "mebibyte")}`,
],
["Total Actor Handle Count", `${memoryTableSummary.total_actor_handles}`],
];
return (
<Grid container className={classes.container}>
{memoryData.map(([label, value]) => (
<LabeledDatum key={label} label={label} datum={value} />
))}
</Grid>
);
};
class MemorySummary extends React.Component<
Props & WithStyles<typeof styles>,
State
> {
state: State = {
expanded: this.props.initialExpanded,
};
toggleExpand = () => {
this.setState((state) => ({
expanded: !state.expanded,
}));
};
render() {
const { classes, memoryTableSummary } = this.props;
const memorySummaries =
memoryTableSummary !== null
? [
"", // Padding
`Total Local Reference Count: ${memoryTableSummary.total_local_ref_count}`,
`Total Pinned In Memory Count: ${memoryTableSummary.total_pinned_in_memory}`,
`Total Used By Pending Tasks Count: ${memoryTableSummary.total_used_by_pending_task}`,
`Total Caputed In Objects Count: ${memoryTableSummary.total_captured_in_objects}`,
`Total Object Size: ${memoryTableSummary.total_object_size} B`,
`Total Actor Handle Count: ${memoryTableSummary.total_actor_handles}`,
"", // Padding
]
: ["No Summary Provided"];
return (
memoryTableSummary !== null && (
<React.Fragment>
<TableRow hover>
{memorySummaries.map((summary, index) => (
<TableCell key={index} className={classes.cell}>
{summary}
</TableCell>
))}
</TableRow>
</React.Fragment>
)
);
}
}
export default withStyles(styles)(MemorySummary);
export default MemorySummary;
@@ -0,0 +1,105 @@
import {
createStyles,
makeStyles,
Paper,
Table,
TableBody,
Theme,
} from "@material-ui/core";
import React from "react";
import { MemoryTableEntry } from "../../../api";
import SortableTableHead, {
HeaderInfo,
} from "../../../common/SortableTableHead";
import { getComparator, Order, stableSort } from "../../../common/tableUtils";
import { MemoryTableRow } from "./MemoryTableRow";
const useMemoryTableStyles = makeStyles((theme: Theme) =>
createStyles({
container: {
margin: theme.spacing(1),
padding: theme.spacing(1),
},
cell: {
padding: theme.spacing(1),
textAlign: "center",
},
}),
);
type memoryColumnId =
| "node_ip_address"
| "pid"
| "type"
| "object_ref"
| "object_size"
| "reference_type"
| "call_site";
const memoryHeaderInfo: HeaderInfo<memoryColumnId>[] = [
{
id: "node_ip_address",
label: "IP Address",
numeric: false,
sortable: true,
},
{ id: "pid", label: "PID", numeric: false, sortable: true },
{ id: "type", label: "Type", numeric: false, sortable: true },
{ id: "object_ref", label: "Object Ref", numeric: false, sortable: true },
{
id: "object_size",
label: "Object Size",
numeric: false,
sortable: true,
},
{
id: "reference_type",
label: "Reference Type",
numeric: false,
sortable: true,
},
{ id: "call_site", label: "Call Site", numeric: false, sortable: true },
];
type MemoryTableProps = {
tableEntries: MemoryTableEntry[];
};
const MemoryTable: React.FC<MemoryTableProps> = ({ tableEntries }) => {
const toggleOrder = () => setOrder(order === "asc" ? "desc" : "asc");
const classes = useMemoryTableStyles();
const [order, setOrder] = React.useState<Order>("asc");
const [orderBy, setOrderBy] = React.useState<memoryColumnId | null>(null);
const comparator = orderBy && getComparator(order, orderBy);
const sortedTableEntries = comparator
? stableSort(tableEntries, comparator)
: tableEntries;
const tableRows = sortedTableEntries.map((tableEntry) => (
<MemoryTableRow memoryTableEntry={tableEntry} key={tableEntry.object_ref} />
));
// Todo(max) add in sorting code
return (
<Paper className={classes.container} elevation={2}>
<Table>
<SortableTableHead
orderBy={orderBy}
order={order}
onRequestSort={(_, property) => {
if (property === orderBy) {
toggleOrder();
} else {
setOrderBy(property);
setOrder("asc");
}
}}
headerInfo={memoryHeaderInfo}
firstColumnEmpty={false}
/>
<TableBody>{tableRows}</TableBody>
</Table>
</Paper>
);
};
export default MemoryTable;
@@ -1,6 +1,7 @@
import { TableRow } from "@material-ui/core";
import React from "react";
import { MemoryTableEntry } from "../../../api";
import { formatByteAmount } from "../../../common/formatUtils";
import { StyledTableCell } from "../../../common/TableCell";
type Props = {
@@ -13,7 +14,7 @@ export const MemoryTableRow = (props: Props) => {
const object_size =
memoryTableEntry.object_size === -1
? "?"
: `${memoryTableEntry.object_size} B`;
: formatByteAmount(memoryTableEntry.object_size, "mebibyte");
const memoryTableEntryValues = [
memoryTableEntry.node_ip_address,
memoryTableEntry.pid,
+20 -4
View File
@@ -31,7 +31,8 @@ from ray.core.generated import core_worker_pb2
from ray.core.generated import core_worker_pb2_grpc
from ray.dashboard.interface import BaseDashboardController
from ray.dashboard.interface import BaseDashboardRouteHandler
from ray.dashboard.memory import construct_memory_table, MemoryTable
from ray.dashboard.memory import construct_memory_table, MemoryTable, \
GroupByType, SortingType
from ray.dashboard.metrics_exporter.client import Exporter
from ray.dashboard.metrics_exporter.client import MetricsExportClient
from ray.dashboard.node_stats import NodeStats
@@ -175,7 +176,9 @@ class DashboardController(BaseDashboardController):
def get_raylet_info(self):
return self._construct_raylet_info()
def get_memory_table_info(self) -> MemoryTable:
def get_memory_table_info(self,
group_by=GroupByType.NODE_ADDRESS,
sort_by=SortingType.OBJECT_SIZE) -> MemoryTable:
# Collecting memory info adds big overhead to the cluster.
# This must be collected only when it is necessary.
self.raylet_stats.include_memory_info = True
@@ -184,7 +187,8 @@ class DashboardController(BaseDashboardController):
data["nodeId"]: data.get("workersStats")
for data in D.values()
}
self.memory_table = construct_memory_table(workers_info_by_node)
self.memory_table = construct_memory_table(
workers_info_by_node, group_by=group_by, sort_by=sort_by)
return self.memory_table
def stop_collecting_memory_table_info(self):
@@ -280,7 +284,19 @@ class DashboardRouteHandler(BaseDashboardRouteHandler):
return await json_response(self.is_dev, result=result)
async def memory_table_info(self, req) -> aiohttp.web.Response:
memory_table = self.dashboard_controller.get_memory_table_info()
group_by = req.query.get("group_by")
sort_by = req.query.get("sort_by")
kwargs = {}
try:
if group_by:
kwargs["group_by"] = GroupByType(group_by)
if sort_by:
kwargs["sort_by"] = SortingType(sort_by)
except ValueError as e:
return aiohttp.web.HTTPBadRequest(reason=str(e))
memory_table = self.dashboard_controller.get_memory_table_info(
**kwargs)
return await json_response(self.is_dev, result=memory_table.__dict__())
async def stop_collecting_memory_table_info(self,
+9 -3
View File
@@ -41,7 +41,8 @@ class SortingType(Enum):
class GroupByType(Enum):
NODE_ADDRESS = 2
NODE_ADDRESS = "node"
STACK_TRACE = "stack_trace"
class ReferenceType:
@@ -94,6 +95,8 @@ class MemoryTableEntry:
def group_key(self, group_by_type: GroupByType) -> str:
if group_by_type == GroupByType.NODE_ADDRESS:
return self.node_address
elif group_by_type == GroupByType.STACK_TRACE:
return self.call_site
else:
raise ValueError(
"group by type {} is invalid.".format(group_by_type))
@@ -272,7 +275,9 @@ class MemoryTable:
return self.__repr__()
def construct_memory_table(workers_info_by_node: dict) -> MemoryTable:
def construct_memory_table(workers_info_by_node: dict,
group_by: GroupByType = GroupByType.NODE_ADDRESS,
sort_by=SortingType.OBJECT_SIZE) -> MemoryTable:
memory_table_entries = []
for node_id, worker_infos in workers_info_by_node.items():
for worker_info in worker_infos:
@@ -290,5 +295,6 @@ def construct_memory_table(workers_info_by_node: dict) -> MemoryTable:
pid=pid)
if memory_table_entry.is_valid():
memory_table_entries.append(memory_table_entry)
memory_table = MemoryTable(memory_table_entries)
memory_table = MemoryTable(
memory_table_entries, group_by_type=group_by, sort_by_type=sort_by)
return memory_table