[Dashboard] Add support for new backend to existing front-end (#11013)

* Trying to commit on top of old code again

* address comment

Co-authored-by: Max Fitton <max@semprehealth.com>
This commit is contained in:
Max Fitton
2020-10-02 12:46:47 -07:00
committed by GitHub
co-authored by Max Fitton
parent 6ed8459f25
commit 5a42ed1848
30 changed files with 612 additions and 691 deletions
+225 -185
View File
@@ -3,6 +3,11 @@ const base =
? "http://localhost:8265"
: window.location.origin;
type APIResponse<T> = {
result: boolean;
msg: string;
data?: T;
};
// TODO(mitchellstern): Add JSON schema validation for the responses.
const get = async <T>(path: string, params: { [key: string]: any }) => {
const url = new URL(path, base);
@@ -11,67 +16,65 @@ const get = async <T>(path: string, params: { [key: string]: any }) => {
}
const response = await fetch(url.toString());
const json = await response.json();
const json: APIResponse<T> = await response.json();
const { result, error } = json;
const { result, msg, data } = json;
if (error !== null) {
throw Error(error);
if (!result) {
throw Error(msg);
}
return result as T;
};
const post = async <T>(path: string, params: { [key: string]: any }) => {
const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(params),
};
const url = new URL(path, base);
const response = await fetch(url.toString(), requestOptions);
const json = await response.json();
const { result, error } = json;
if (error !== null) {
throw Error(error);
}
return result as T;
return data as T;
};
export type RayConfigResponse = {
min_workers: number;
max_workers: number;
initial_workers: number;
autoscaling_mode: string;
idle_timeout_minutes: number;
head_type: string;
worker_type: string;
minWorkers: number;
maxWorkers: number;
initialWorkers: number;
autoscalingMode: string;
idleTimeoutMinutes: number;
headType: string;
workerType: string;
};
export const getRayConfig = () => get<RayConfigResponse>("/api/ray_config", {});
export type NodeInfoResponseWorker = {
export type Worker = {
pid: number;
create_time: number;
cmdline: string[];
cpu_percent: number;
cpu_times: {
system: number;
children_system: number;
user: number;
children_user: number;
};
memory_info: {
pageins: number;
pfaults: number;
vms: number;
workerId: string;
createTime: number;
memoryInfo: {
rss: number;
vms: number;
shared: number;
text: number;
lib: number;
data: number;
dirty: Number;
};
cmdline: string[];
cpuTimes: {
user: number;
system: number;
childrenUser: number;
childrenSystem: number;
iowait: number;
};
cpuPercent: number;
logCount: number;
errorCount: number;
language: string;
jobId: string;
coreWorkerStats: CoreWorkerStats[];
};
export type CoreWorkerStats = {
ipAddress: string;
port: number;
usedResources?: { [key: string]: ResourceAllocations };
numExecutedTasks: number;
workerId: string;
// We need the below but Ant's API does not yet support it.
};
export type GPUProcessStats = {
@@ -79,7 +82,7 @@ export type GPUProcessStats = {
// utilization of a single process of a single GPU.
username: string;
command: string;
gpu_memory_usage: number;
gpuMemoryUsage: number;
pid: number;
};
@@ -87,43 +90,89 @@ export type GPUStats = {
// This represents stats fetched from a node about a single GPU
uuid: string;
name: string;
temperature_gpu: number;
fan_speed: number;
utilization_gpu: number;
power_draw: number;
enforced_power_limit: number;
memory_used: number;
memory_total: number;
processes: Array<GPUProcessStats>;
temperatureGpu: number;
fanSpeed: number;
utilizationGpu: number;
powerDraw: number;
enforcedPowerLimit: number;
memoryUsed: number;
memoryTotal: number;
processes: GPUProcessStats[];
};
export type NodeSummary = BaseNodeInfo;
export type NodeDetails = {
workers: Worker[];
raylet: RayletData;
} & BaseNodeInfo;
export type RayletData = {
// Merger of GCSNodeStats and GetNodeStatsReply
// GetNodeStatsReply fields.
// Note workers are in an array in NodeDetails
objectStoreUsedMemory: number;
objectStoreAvailableMemory: number;
numWorkers: number;
// GCSNodeStats fields
nodeId: number;
nodeManagerAddress: string;
rayletSocketName: string;
objectStoreSocketName: string;
nodeManagerPort: number;
objectManagerPort: number;
state: "ALIVE" | "DEAD";
nodeManagerHostname: string;
metricsExportPort: number;
};
export type ViewData = {
viewName: string;
measures: Measure[];
};
export type Measure = {
tags: string; // e.g. "Tag1:Value1,Tag2:Value2,Tag3:Value3"
intValue?: number;
doubleValue?: number;
distributionMin?: number;
distributionMean?: number;
distributionMax?: number;
distributionCount?: number;
distributionBucketBoundaries?: number[];
distributionBucketCounts?: number[];
};
type BaseNodeInfo = {
now: number;
hostname: string;
ip: string;
bootTime: number; // System boot time expressed in seconds since epoch
cpu: number; // System-wide CPU utilization expressed as a percentage
cpus: [number, number]; // Number of logical CPUs and physical CPUs
gpus: Array<GPUStats>; // GPU stats fetched from node, 1 entry per GPU
mem: [number, number, number]; // Total, available, and used percentage of memory
disk: {
[dir: string]: {
total: number;
free: number;
used: number;
percent: number;
};
};
loadAvg: [[number, number, number], [number, number, number]];
net: [number, number]; // Sent and received network traffic in bytes / second
logCount: number;
errorCount: number;
};
export type NodeInfoResponse = {
clients: Array<{
now: number;
hostname: string;
ip: string;
boot_time: number; // System boot time expressed in seconds since epoch
cpu: number; // System-wide CPU utilization expressed as a percentage
cpus: [number, number]; // Number of logical CPUs and physical CPUs
gpus: Array<GPUStats>; // GPU stats fetched from node, 1 entry per GPU
mem: [number, number, number]; // Total, available, and used percentage of memory
disk: {
[path: string]: {
total: number;
free: number;
used: number;
percent: number;
};
};
load_avg: [[number, number, number], [number, number, number]];
net: [number, number]; // Sent and received network traffic in bytes / second
log_count?: { [pid: string]: number };
error_count?: { [pid: string]: number };
workers: Array<NodeInfoResponseWorker>;
}>;
clients: NodeDetails[];
};
export const getNodeInfo = () => get<NodeInfoResponse>("/api/node_info", {});
export const getNodeInfo = () =>
get<NodeInfoResponse>("/nodes", { view: "details" });
export type ResourceSlot = {
slot: number;
@@ -134,44 +183,34 @@ export type ResourceAllocations = {
resourceSlots: ResourceSlot[];
};
export type RayletCoreWorkerStats = {
usedResources: {
[key: string]: ResourceAllocations;
};
};
export type RayletWorkerStats = {
pid: number;
isDriver?: boolean;
coreWorkerStats: RayletCoreWorkerStats;
};
export const getActorGroups = () =>
get<ActorsResponse>("logical/actor_groups", {});
export enum ActorState {
// These two are virtual states that we air because there is
// an existing task to create an actor
Infeasible = -2, // Actor task is waiting on resources (e.g. RAM, CPUs or GPUs) that the cluster does not have
PendingResources = -1, // Actor task is waiting on resources the cluster has but are in-use
Infeasible = "INFEASIBLE", // Actor task is waiting on resources (e.g. RAM, CPUs or GPUs) that the cluster does not have
PendingResources = "PENDING_RESOURCES", // Actor task is waiting on resources the cluster has but are in-use
// The rest below are "official" GCS actor states
DependenciesUnready = 0, // Actor is pending on an argument to be ready
PendingCreation = 1, // Actor creation is running
Alive = 2, // Actor is alive and handling tasks
Restarting = 3, // Actor died and is being restarted
Dead = 4, // Actor died and is not being restarted
DependenciesUnready = "PENDING", // Actor is pending on an argument to be ready
PendingCreation = "CREATING", // Actor creation is running
Alive = "ALIVE", // Actor is alive and handling tasks
Restarting = "RESTARTING", // Actor died and is being restarted
Dead = "DEAD", // Actor died and is not being restarted
}
export type ActorInfo = FullActorInfo | ActorTaskInfo;
export type FullActorInfo = {
actorId: string;
actorTitle: string;
averageTaskExecutionSpeed: number;
children?: ActorInfo[];
actorConstructor: string;
actorClass: string;
ipAddress: string;
jobId: string;
nodeId: string;
numExecutedTasks: number;
numLocalObjects: number;
numObjectRefsInScope: number;
numExecutedTasks?: number;
numLocalObjects?: number;
numObjectRefsInScope?: number;
pid: number;
port: number;
state:
@@ -180,9 +219,9 @@ export type FullActorInfo = {
| ActorState.Dead
| ActorState.DependenciesUnready
| ActorState.PendingCreation;
taskQueueLength: number;
taskQueueLength?: number;
timestamp: number;
usedObjectStoreMemory: number;
usedObjectStoreMemory?: number;
usedResources: { [key: string]: ResourceAllocations };
currentTaskDesc?: string;
numPendingTasks?: number;
@@ -190,8 +229,8 @@ export type FullActorInfo = {
};
export type ActorTaskInfo = {
actorId?: string;
actorTitle?: string;
actorId: string;
actorClass: string;
requiredResources?: { [key: string]: number };
state: ActorState.Infeasible | ActorState.PendingResources;
};
@@ -221,52 +260,41 @@ export type ActorGroup = {
summary: ActorGroupSummary;
};
export type RayletInfoResponse = {
nodes: {
[ip: string]: {
extraInfo?: string;
workersStats: Array<RayletWorkerStats>;
};
};
export type ActorsResponse = {
actorGroups: {
[groupKey: string]: ActorGroup;
};
plasmaStats: {
[ip: string]: PlasmaStats;
[key: string]: ActorGroup;
};
};
export type PlasmaStats = {
object_store_num_local_objects: number;
object_store_available_memory: number;
object_store_used_memory: number;
};
export const getRayletInfo = () =>
get<RayletInfoResponse>("/api/raylet_info", {});
export type ErrorsResponse = {
[pid: string]: Array<{
errors: ErrorsByPid;
};
export type ErrorsByPid = {
[pid: string]: {
message: string;
timestamp: number;
type: string;
}>;
}[];
};
export const getErrors = (hostname: string, pid: number | null) =>
get<ErrorsResponse>("/api/errors", {
hostname,
pid: pid === null ? "" : pid,
export const getErrors = (nodeIp: string, pid: number | null) =>
get<ErrorsResponse>("/node_errors", {
nodeIp,
pid: pid ?? "",
});
export type LogsResponse = {
logs: LogsByPid;
};
export type LogsByPid = {
[pid: string]: string[];
};
export const getLogs = (hostname: string, pid: number | null) =>
get<LogsResponse>("/api/logs", {
hostname,
pid: pid === null ? "" : pid,
export const getLogs = (nodeIp: string, pid: number | null) =>
get<LogsResponse>("/node_logs", {
ip: nodeIp,
pid: pid ?? "",
});
export type LaunchProfilingResponse = string;
@@ -302,34 +330,34 @@ export const launchKillActor = (
actorIpAddress: string,
actorPort: number,
) =>
get<object>("/api/kill_actor", {
get<{}>("/logical/kill_actor", {
// make sure object is okay
actor_id: actorId,
ip_address: actorIpAddress,
actorId: actorId,
ipAddress: actorIpAddress,
port: actorPort,
});
export type TuneTrial = {
date: string;
episodes_total: string;
experiment_id: string;
experiment_tag: string;
episodesTotal: string;
experimentId: string;
experimentTag: string;
hostname: string;
iterations_since_restore: number;
iterationsSinceRestore: number;
logdir: string;
node_ip: string;
nodeIp: string;
pid: number;
time_since_restore: number;
time_this_iter_s: number;
time_total_s: number;
timeSinceRestore: number;
timeThisIterS: number;
timeTotalS: number;
timestamp: number;
timesteps_since_restore: number;
timesteps_total: number;
training_iteration: number;
start_time: string;
timestepsSinceRestore: number;
timestepsTotal: number;
trainingIteration: number;
startTime: string;
status: string;
trial_id: string | number;
job_id: string;
trialId: string | number;
jobId: string;
params: { [key: string]: string | number };
metrics: { [key: string]: string | number };
error: string;
@@ -337,59 +365,72 @@ export type TuneTrial = {
export type TuneError = {
text: string;
job_id: string;
trial_id: string;
jobId: string;
trialId: string;
};
export type TuneJobResponse = {
trial_records: { [key: string]: TuneTrial };
result: TuneJob;
};
export type TuneJob = {
trialRecords: { [key: string]: TuneTrial };
errors: { [key: string]: TuneError };
tensorboard: {
tensorboard_current: boolean;
tensorboard_enabled: boolean;
tensorboardCurrent: boolean;
tensorboardEnabled: boolean;
};
};
export const getTuneInfo = () => get<TuneJobResponse>("/api/tune_info", {});
export const getTuneInfo = () => get<TuneJobResponse>("/tune/info", {});
export type TuneAvailability = {
available: boolean;
trialsAvailable: boolean;
};
export type TuneAvailabilityResponse = {
available: boolean;
trials_available: boolean;
result: TuneAvailability;
};
export const getTuneAvailability = () =>
get<TuneAvailabilityResponse>("/api/tune_availability", {});
get<TuneAvailabilityResponse>("/tune/availability", {});
export type TuneSetExperimentReponse = {
export type TuneSetExperimentResponse = {
experiment: string;
};
export const setTuneExperiment = (experiment: string) =>
post<TuneSetExperimentReponse>("/api/set_tune_experiment", {
get<TuneSetExperimentResponse>("/tune/set_experiment", {
experiment: experiment,
});
export const enableTuneTensorBoard = () =>
post<{}>("/api/enable_tune_tensorboard", {});
get<{}>("/tune/enable_tensorboard", {});
export type MemoryTableSummary = {
total_actor_handles: number;
total_captured_in_objects: number;
total_local_ref_count: number;
totalActorHandles: number;
totalCapturedInObjects: number;
totalLocalRefCount: number;
// The measurement is B.
total_object_size: number;
total_pinned_in_memory: number;
total_used_by_pending_task: number;
totalObjectSize: number;
totalPinnedInMemory: number;
totalUsedByPendingTask: number;
};
export type MemoryTableEntry = {
node_ip_address: string;
nodeIpAddress: string;
pid: number;
type: string;
object_ref: string;
object_size: number;
reference_type: string;
call_site: string;
objectRef: string;
objectSize: number;
referenceType: string;
callSite: string;
};
export type MemoryTable = {
group: MemoryTableGroups;
summary: MemoryTableSummary;
};
export type MemoryTableGroups = {
@@ -402,8 +443,7 @@ export type MemoryTableGroup = {
};
export type MemoryTableResponse = {
group: MemoryTableGroups;
summary: MemoryTableSummary;
memoryTable: MemoryTable;
};
// This doesn't return anything.
@@ -412,10 +452,10 @@ export type StopMemoryTableResponse = {};
export type MemoryGroupByKey = "node" | "stack_trace" | "";
export const getMemoryTable = async (groupByKey: MemoryGroupByKey) => {
return get<MemoryTableResponse>("/api/memory_table", {
group_by: groupByKey,
return get<MemoryTableResponse>("/memory/memory_table", {
groupBy: groupByKey,
});
};
export const stopMemoryTableCollection = () =>
get<StopMemoryTableResponse>("/api/stop_memory_table", {});
export const setMemoryTableCollection = (value: boolean) =>
get<{}>("/memory/set_fetch", { shouldFetch: value });
@@ -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";
@@ -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,
@@ -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);
render() {
const { classes, tab, tuneAvailability } = this.props;
const tabs = this.tabs.slice();
// if Tune information is not available, remove Tune tab from the dashboard
if (tuneAvailability === null || !tuneAvailability.available) {
tabs.splice(4);
// 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]);
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>
);
const handleTabChange = (_: any, value: number) => dispatch(setTab(value));
const tabs = allTabs.slice();
// 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;
@@ -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]))
@@ -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 " +
@@ -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 " +
@@ -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) && (
@@ -5,7 +5,7 @@ import LabeledDatum from "../../../common/LabeledDatum";
import ActorStateRepr from "./ActorStateRepr";
type ActorDetailsPaneProps = {
actorTitle: string;
actorClass: string;
actorState: ActorState;
actorDetails: {
label: string;
@@ -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} />
@@ -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>
@@ -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>
@@ -19,7 +19,7 @@ import {
getMemoryTable,
MemoryGroupByKey,
MemoryTableResponse,
stopMemoryTableCollection,
setMemoryTableCollection,
} from "../../../api";
import { StoreState } from "../../../store";
import { dashboardActions } from "../state";
@@ -68,11 +68,7 @@ const useMemoryInfoStyles = makeStyles((theme: Theme) =>
}),
);
const memoryInfoSelector = (state: StoreState) => ({
tab: state.dashboard.tab,
memoryTable: state.dashboard.memoryTable,
shouldObtainMemoryTable: state.dashboard.shouldObtainMemoryTable,
});
const memoryTableSelector = (state: StoreState) => state.dashboard.memoryTable;
const fetchMemoryTable = (
groupByKey: MemoryGroupByKey,
@@ -85,7 +81,7 @@ const fetchMemoryTable = (
};
const MemoryInfo: React.FC<{}> = () => {
const { memoryTable } = useSelector(memoryInfoSelector);
const memoryTable = useSelector(memoryTableSelector);
const dispatch = useDispatch();
const [paused, setPaused] = useState(false);
@@ -94,6 +90,13 @@ const MemoryInfo: React.FC<{}> = () => {
const classes = useMemoryInfoStyles();
const [groupBy, setGroupBy] = useState<MemoryGroupByKey>("node");
// Turn memory collection on render
useEffect(() => {
setMemoryTableCollection(true);
return () => {
setMemoryTableCollection(false);
};
}, []);
// Set up polling memory data
const fetchData = useCallback(
fetchMemoryTable(groupBy, (resp) =>
@@ -118,8 +121,13 @@ const MemoryInfo: React.FC<{}> = () => {
if (!memoryTable) {
return (
<Typography variant="h5" align="center">
Loading memory information
<Typography color="textSecondary">Loading memory information</Typography>
);
}
if (Object.keys(memoryTable.group).length === 0) {
return (
<Typography color="textSecondary">
Finished loading, but have found no memory data yet.
</Typography>
);
}
@@ -162,9 +170,7 @@ const MemoryInfo: React.FC<{}> = () => {
color="primary"
className={classes.pauseButton}
onClick={() => {
if (!paused) {
stopMemoryTableCollection();
}
setMemoryTableCollection(!paused);
setPaused(!paused);
}}
>
@@ -28,24 +28,21 @@ const MemorySummary: React.FC<MemorySummaryProps> = ({
}) => {
const classes = useMemorySummaryStyles();
const memoryData = [
[
"Total Local Reference Count",
`${memoryTableSummary.total_local_ref_count}`,
],
["Pinned in Memory Count", `${memoryTableSummary.total_pinned_in_memory}`],
["Total Local Reference Count", `${memoryTableSummary.totalLocalRefCount}`],
["Pinned in Memory Count", `${memoryTableSummary.totalPinnedInMemory}`],
[
"Total Used by Pending Tasks Count",
`${memoryTableSummary.total_used_by_pending_task}`,
`${memoryTableSummary.totalUsedByPendingTask}`,
],
[
"Total Captured in Objects Count",
`${memoryTableSummary.total_captured_in_objects}`,
`${memoryTableSummary.totalCapturedInObjects}`,
],
[
"Total Memory Used by Objects",
`${formatByteAmount(memoryTableSummary.total_object_size, "mebibyte")}`,
`${formatByteAmount(memoryTableSummary.totalObjectSize, "mebibyte")}`,
],
["Total Actor Handle Count", `${memoryTableSummary.total_actor_handles}`],
["Total Actor Handle Count", `${memoryTableSummary.totalActorHandles}`],
];
return (
@@ -29,37 +29,37 @@ const useMemoryTableStyles = makeStyles((theme: Theme) =>
);
type memoryColumnId =
| "node_ip_address"
| "nodeIpAddress"
| "pid"
| "type"
| "object_ref"
| "object_size"
| "reference_type"
| "call_site";
| "objectRef"
| "objectSize"
| "referenceType"
| "callSite";
const memoryHeaderInfo: HeaderInfo<memoryColumnId>[] = [
{
id: "node_ip_address",
id: "nodeIpAddress",
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: "objectRef", label: "Object Ref", numeric: false, sortable: true },
{
id: "object_size",
id: "objectSize",
label: "Object Size",
numeric: false,
sortable: true,
},
{
id: "reference_type",
id: "referenceType",
label: "Reference Type",
numeric: false,
sortable: true,
},
{ id: "call_site", label: "Call Site", numeric: false, sortable: true },
{ id: "callSite", label: "Call Site", numeric: false, sortable: true },
];
type MemoryTableProps = {
@@ -76,7 +76,7 @@ const MemoryTable: React.FC<MemoryTableProps> = ({ tableEntries }) => {
? stableSort(tableEntries, comparator)
: tableEntries;
const tableRows = sortedTableEntries.map((tableEntry) => (
<MemoryTableRow memoryTableEntry={tableEntry} key={tableEntry.object_ref} />
<MemoryTableRow memoryTableEntry={tableEntry} key={tableEntry.objectRef} />
));
// Todo(max) add in sorting code
return (
@@ -11,17 +11,17 @@ type Props = {
export const MemoryTableRow = (props: Props) => {
const { memoryTableEntry } = props;
const object_size =
memoryTableEntry.object_size === -1
memoryTableEntry.objectSize === -1
? "?"
: formatByteAmount(memoryTableEntry.object_size, "mebibyte");
: formatByteAmount(memoryTableEntry.objectSize, "mebibyte");
const memoryTableEntryValues = [
memoryTableEntry.node_ip_address,
memoryTableEntry.nodeIpAddress,
memoryTableEntry.pid,
memoryTableEntry.type,
memoryTableEntry.object_ref,
memoryTableEntry.objectRef,
object_size,
memoryTableEntry.reference_type,
memoryTableEntry.call_site,
memoryTableEntry.referenceType,
memoryTableEntry.callSite,
];
return (
<TableRow hover>
@@ -10,7 +10,6 @@ import {
} from "@material-ui/core";
import React, { useState } from "react";
import { useSelector } from "react-redux";
import { RayletInfoResponse } from "../../../api";
import SortableTableHead, {
HeaderInfo,
} from "../../../common/SortableTableHead";
@@ -68,24 +67,17 @@ const makeGroupedTableContents = (
nodes: Node[],
sortWorkerComparator: any,
sortGroupComparator: any,
rayletInfo: RayletInfoResponse | null,
nodeInfoFeatures: NodeInfoFeature[],
) => {
const sortedGroups = sortGroupComparator
? stableSort(nodes, sortGroupComparator)
: nodes;
return sortedGroups.map((node) => {
const plasmaStats = rayletInfo?.plasmaStats?.[node.ip];
const workerFeatureData: WorkerFeatureData[] = node.workers.map(
(worker) => {
const rayletWorker =
rayletInfo?.nodes?.[node.ip]?.workersStats?.find(
(workerStats) => workerStats.pid === worker.pid,
) || null;
return {
node,
worker,
rayletWorker,
};
},
);
@@ -100,7 +92,6 @@ const makeGroupedTableContents = (
node={node}
workerFeatureData={sortedClusterWorkers}
features={nodeInfoFeatures}
plasmaStats={plasmaStats}
initialExpanded={nodes.length <= 1}
/>
);
@@ -110,7 +101,6 @@ const makeGroupedTableContents = (
const makeUngroupedTableContents = (
nodes: Node[],
sortWorkerComparator: any,
rayletInfo: RayletInfoResponse | null,
nodeInfoFeatures: NodeInfoFeature[],
) => {
const workerInfoFeatures = nodeInfoFeatures.map(
@@ -118,14 +108,9 @@ const makeUngroupedTableContents = (
);
const allWorkerFeatures: WorkerFeatureData[] = nodes.flatMap((node) => {
return node.workers.map((worker) => {
const rayletWorker =
rayletInfo?.nodes?.[node.ip]?.workersStats?.find(
(workerStats) => workerStats.pid === worker.pid,
) || null;
return {
node: node,
node,
worker,
rayletWorker,
};
});
});
@@ -154,13 +139,10 @@ const useNodeInfoStyles = makeStyles((theme: Theme) =>
}),
);
const nodeInfoSelector = (state: StoreState) => ({
nodeInfo: state.dashboard.nodeInfo,
rayletInfo: state.dashboard.rayletInfo,
});
const nodesSelector = (state: StoreState) => state.dashboard?.nodeInfo?.clients;
type DialogState = {
hostname: string;
nodeIp: string;
pid: number | null;
} | null;
@@ -188,13 +170,11 @@ const NodeInfo: React.FC<{}> = () => {
const toggleOrder = () => setOrder(order === "asc" ? "desc" : "asc");
const [orderBy, setOrderBy] = React.useState<nodeInfoColumnId | null>(null);
const classes = useNodeInfoStyles();
const { nodeInfo, rayletInfo } = useSelector(nodeInfoSelector);
if (nodeInfo === null || rayletInfo === null) {
const nodes = useSelector(nodesSelector);
if (!nodes) {
return <Typography color="textSecondary">Loading...</Typography>;
}
const clusterTotalWorkers = sum(
nodeInfo.clients.map((c) => c.workers.length),
);
const clusterTotalWorkers = sum(nodes.map((n) => n.workers.length));
const nodeInfoFeatures: NodeInfoFeature[] = [
hostFeature,
workersFeature,
@@ -207,8 +187,8 @@ const NodeInfo: React.FC<{}> = () => {
diskFeature,
sentFeature,
receivedFeature,
makeLogsFeature((hostname, pid) => setLogDialog({ hostname, pid })),
makeErrorsFeature((hostname, pid) => setErrorDialog({ hostname, pid })),
makeLogsFeature((nodeIp, pid) => setLogDialog({ nodeIp, pid })),
makeErrorsFeature((nodeIp, pid) => setErrorDialog({ nodeIp, pid })),
];
const sortNodeAccessor = nodeInfoFeatures.find(
(feature) => feature.id === orderBy,
@@ -222,18 +202,12 @@ const NodeInfo: React.FC<{}> = () => {
sortWorkerAccessor && getFnComparator(order, sortWorkerAccessor);
const tableContents = isGrouped
? makeGroupedTableContents(
nodeInfo.clients,
nodes,
sortWorkerComparator,
sortNodeComparator,
rayletInfo,
nodeInfoFeatures,
)
: makeUngroupedTableContents(
nodeInfo.clients,
sortWorkerComparator,
rayletInfo,
nodeInfoFeatures,
);
: makeUngroupedTableContents(nodes, sortWorkerComparator, nodeInfoFeatures);
return (
<React.Fragment>
<FormControlLabel
@@ -265,8 +239,7 @@ const NodeInfo: React.FC<{}> = () => {
{tableContents}
<TotalRow
clusterTotalWorkers={clusterTotalWorkers}
nodes={nodeInfo.clients}
plasmaStats={Object.values(rayletInfo.plasmaStats)}
nodes={nodes}
features={nodeInfoFeatures.map(
(feature) => feature.ClusterFeatureRenderFn,
)}
@@ -276,14 +249,14 @@ const NodeInfo: React.FC<{}> = () => {
{logDialog !== null && (
<Logs
clearLogDialog={() => setLogDialog(null)}
hostname={logDialog.hostname}
nodeIp={logDialog.nodeIp}
pid={logDialog.pid}
/>
)}
{errorDialog !== null && (
<Errors
clearErrorDialog={() => setErrorDialog(null)}
hostname={errorDialog.hostname}
nodeIp={errorDialog.nodeIp}
pid={errorDialog.pid}
/>
)}
@@ -9,7 +9,7 @@ import AddIcon from "@material-ui/icons/Add";
import RemoveIcon from "@material-ui/icons/Remove";
import classNames from "classnames";
import React, { useState } from "react";
import { NodeInfoResponse, PlasmaStats } from "../../../api";
import { NodeInfoResponse } from "../../../api";
import { StyledTableCell } from "../../../common/TableCell";
import { NodeInfoFeature, WorkerFeatureData } from "./features/types";
import { NodeWorkerRow } from "./NodeWorkerRow";
@@ -45,7 +45,6 @@ type NodeRowGroupProps = {
features: NodeInfoFeature[];
node: Node;
rayletInfo?: string;
plasmaStats?: PlasmaStats;
workerFeatureData: WorkerFeatureData[];
initialExpanded: boolean;
};
@@ -56,7 +55,6 @@ const NodeRowGroup: React.FC<NodeRowGroupProps> = ({
initialExpanded,
rayletInfo,
workerFeatureData,
plasmaStats,
}) => {
const [expanded, setExpanded] = useState<boolean>(initialExpanded);
const toggleExpand = () => setExpanded(!expanded);
@@ -65,7 +63,7 @@ const NodeRowGroup: React.FC<NodeRowGroupProps> = ({
const FeatureComponent = nodeInfoFeature.NodeFeatureRenderFn;
return (
<StyledTableCell className={classes.cell} key={i}>
<FeatureComponent node={node} plasmaStats={plasmaStats} />
<FeatureComponent node={node} />
</StyledTableCell>
);
});
@@ -12,17 +12,13 @@ export const NodeWorkerRow: React.FC<NodeWorkerRowProps> = ({
features,
data,
}) => {
const { node, worker, rayletWorker } = data;
const { node, worker } = data;
return (
<TableRow hover>
<StyledTableCell />
{features.map((WorkerFeature, index) => (
<StyledTableCell key={index}>
<WorkerFeature
node={node}
worker={worker}
rayletWorker={rayletWorker}
/>
<WorkerFeature node={node} worker={worker} />
</StyledTableCell>
))}
</TableRow>
@@ -7,7 +7,7 @@ import {
} from "@material-ui/core";
import LayersIcon from "@material-ui/icons/Layers";
import React from "react";
import { NodeInfoResponse, PlasmaStats } from "../../../api";
import { NodeInfoResponse } from "../../../api";
import { StyledTableCell } from "../../../common/TableCell";
import { ClusterFeatureRenderFn } from "./features/types";
@@ -33,16 +33,11 @@ const useTotalRowStyles = makeStyles((theme: Theme) =>
type TotalRowProps = {
nodes: NodeInfoResponse["clients"];
plasmaStats: PlasmaStats[];
clusterTotalWorkers: number;
features: (ClusterFeatureRenderFn | undefined)[];
};
const TotalRow: React.FC<TotalRowProps> = ({
nodes,
features,
plasmaStats,
}) => {
const TotalRow: React.FC<TotalRowProps> = ({ nodes, features }) => {
const classes = useTotalRowStyles();
return (
<TableRow hover>
@@ -52,7 +47,7 @@ const TotalRow: React.FC<TotalRowProps> = ({
{features.map((ClusterFeature, index) =>
ClusterFeature ? (
<TableCell className={classes.cell} key={index}>
<ClusterFeature nodes={nodes} plasmaStats={plasmaStats} />
<ClusterFeature nodes={nodes} />
</TableCell>
) : (
<StyledTableCell key={index} />
@@ -7,7 +7,7 @@ import {
WithStyles,
} from "@material-ui/core";
import React from "react";
import { ErrorsResponse, getErrors } from "../../../../../api";
import { ErrorsByPid, getErrors } from "../../../../../api";
import DialogWithTitle from "../../../../../common/DialogWithTitle";
import NumberedLines from "../../../../../common/NumberedLines";
@@ -34,12 +34,12 @@ const styles = (theme: Theme) =>
type Props = {
clearErrorDialog: () => void;
hostname: string;
nodeIp: string;
pid: number | null;
};
type State = {
result: ErrorsResponse | null;
result: ErrorsByPid | null;
error: string | null;
};
@@ -51,16 +51,16 @@ class Errors extends React.Component<Props & WithStyles<typeof styles>, State> {
async componentDidMount() {
try {
const { hostname, pid } = this.props;
const result = await getErrors(hostname, pid);
this.setState({ result, error: null });
const { nodeIp, pid } = this.props;
const result = await getErrors(nodeIp, pid);
this.setState({ result: result.errors, error: null });
} catch (error) {
this.setState({ result: null, error: error.toString() });
}
}
render() {
const { classes, clearErrorDialog, hostname } = this.props;
const { classes, clearErrorDialog, nodeIp } = this.props;
const { result, error } = this.state;
return (
@@ -73,7 +73,7 @@ class Errors extends React.Component<Props & WithStyles<typeof styles>, State> {
Object.entries(result).map(([pid, errors]) => (
<React.Fragment key={pid}>
<Typography className={classes.header}>
{hostname} (PID: {pid})
{nodeIp} (PID: {pid})
</Typography>
{errors.length > 0 ? (
errors.map(({ message, timestamp }, index) => (
@@ -7,7 +7,7 @@ import {
withStyles,
} from "@material-ui/core";
import React from "react";
import { getLogs, LogsResponse } from "../../../../../api";
import { getLogs, LogsByPid } from "../../../../../api";
import DialogWithTitle from "../../../../../common/DialogWithTitle";
import NumberedLines from "../../../../../common/NumberedLines";
@@ -29,12 +29,12 @@ const styles = (theme: Theme) =>
type Props = {
clearLogDialog: () => void;
hostname: string;
nodeIp: string;
pid: number | null;
};
type State = {
result: LogsResponse | null;
result: LogsByPid | null;
error: string | null;
};
@@ -46,16 +46,16 @@ class Logs extends React.Component<Props & WithStyles<typeof styles>, State> {
async componentDidMount() {
try {
const { hostname, pid } = this.props;
const result = await getLogs(hostname, pid);
this.setState({ result, error: null });
const { nodeIp, pid } = this.props;
const result = await getLogs(nodeIp, pid);
this.setState({ result: result.logs, error: null });
} catch (error) {
this.setState({ result: null, error: error.toString() });
}
}
render() {
const { classes, clearLogDialog, hostname } = this.props;
const { classes, clearLogDialog, nodeIp } = this.props;
const { result, error } = this.state;
return (
@@ -68,7 +68,7 @@ class Logs extends React.Component<Props & WithStyles<typeof styles>, State> {
Object.entries(result).map(([pid, lines]) => (
<React.Fragment key={pid}>
<Typography className={classes.header}>
{hostname} (PID: {pid})
{nodeIp} (PID: {pid})
</Typography>
{lines.length > 0 ? (
<div className={classes.log}>
@@ -37,14 +37,14 @@ export const nodeCPUAccessor: Accessor<NodeFeatureData> = ({ node }) => {
export const WorkerCPU: WorkerFeatureRenderFn = ({ worker }) => (
<div style={{ minWidth: 60 }}>
<UsageBar
percent={worker.cpu_percent}
text={`${worker.cpu_percent.toFixed(1)}%`}
percent={worker.cpuPercent}
text={`${worker.cpuPercent.toFixed(1)}%`}
/>
</div>
);
export const workerCPUAccessor: Accessor<WorkerFeatureData> = ({ worker }) => {
return worker.cpu_percent;
return worker.cpuPercent;
};
const cpuFeature: NodeInfoFeature = {
@@ -5,7 +5,6 @@ import { Accessor } from "../../../../common/tableUtils";
import { sum } from "../../../../common/util";
import {
ClusterFeatureRenderFn,
Node,
NodeFeatureData,
NodeFeatureRenderFn,
NodeInfoFeature,
@@ -13,11 +12,8 @@ import {
WorkerFeatureRenderFn,
} from "./types";
const nodeErrCount = (node: Node) =>
node.error_count ? sum(Object.values(node.error_count)) : 0;
const ClusterErrors: ClusterFeatureRenderFn = ({ nodes }) => {
const totalErrCount = sum(nodes.map(nodeErrCount));
const totalErrCount = sum(nodes.map((node) => node.errorCount));
return totalErrCount === 0 ? (
<Typography color="textSecondary" component="span" variant="inherit">
No errors
@@ -31,30 +27,28 @@ const ClusterErrors: ClusterFeatureRenderFn = ({ nodes }) => {
};
const makeNodeErrors = (
setErrorDialog: (hostname: string, pid: number | null) => void,
setErrorDialog: (nodeIp: string, pid: number | null) => void,
): NodeFeatureRenderFn => ({ node }) => {
const nodeErrorCount = nodeErrCount(node);
return nodeErrorCount === 0 ? (
return node.errorCount === 0 ? (
<Typography color="textSecondary" component="span" variant="inherit">
No errors
</Typography>
) : (
<SpanButton onClick={() => setErrorDialog(node.hostname, null)}>
View all errors ({nodeErrorCount.toLocaleString()})
<SpanButton onClick={() => setErrorDialog(node.ip, null)}>
View all errors ({node.errorCount.toLocaleString()})
</SpanButton>
);
};
const nodeErrorsAccessor: Accessor<NodeFeatureData> = ({ node }) =>
nodeErrCount(node);
node.errorCount;
const makeWorkerErrors = (
setErrorDialog: (hostname: string, pid: number | null) => void,
setErrorDialog: (nodeIp: string, pid: number | null) => void,
): WorkerFeatureRenderFn => ({ node, worker }) => {
const workerErrorCount = node.error_count?.[worker.pid] || 0;
return workerErrorCount !== 0 ? (
<SpanButton onClick={() => setErrorDialog(node.hostname, worker.pid)}>
View errors ({workerErrorCount.toLocaleString()})
return worker.errorCount !== 0 ? (
<SpanButton onClick={() => setErrorDialog(node.ip, worker.pid)}>
View errors ({worker.errorCount.toLocaleString()})
</SpanButton>
) : (
<Typography color="textSecondary" component="span" variant="inherit">
@@ -63,11 +57,11 @@ const makeWorkerErrors = (
);
};
const workerErrorsAccessor: Accessor<WorkerFeatureData> = ({ node, worker }) =>
node.error_count?.[worker.pid] || 0;
const workerErrorsAccessor: Accessor<WorkerFeatureData> = ({ worker }) =>
worker.errorCount;
const makeErrorsFeature = (
setErrorDialog: (hostname: string, pid: number | null) => void,
setErrorDialog: (nodeIp: string, pid: number | null) => void,
): NodeInfoFeature => ({
id: "errors",
ClusterFeatureRenderFn: ClusterErrors,
@@ -1,6 +1,6 @@
import { Box, Tooltip, Typography } from "@material-ui/core";
import React from "react";
import { GPUStats, RayletWorkerStats, ResourceSlot } from "../../../../api";
import { GPUStats, ResourceSlot, Worker } from "../../../../api";
import { RightPaddedTypography } from "../../../../common/CustomTypography";
import { Accessor } from "../../../../common/tableUtils";
@@ -35,7 +35,7 @@ const nodeGPUUtilization = (node: Node): number => {
if (!node.gpus || node.gpus.length === 0) {
return NaN;
}
const utilizationSum = sum(node.gpus.map((gpu) => gpu.utilization_gpu));
const utilizationSum = sum(node.gpus.map((gpu) => gpu.utilizationGpu));
const avgUtilization = utilizationSum / node.gpus.length;
return avgUtilization;
};
@@ -88,8 +88,8 @@ const NodeGPUEntry: React.FC<NodeGPUEntryProps> = ({ gpu, slot }) => {
<RightPaddedTypography variant="body1">[{slot}]:</RightPaddedTypography>
</Tooltip>
<UsageBar
percent={gpu.utilization_gpu}
text={`${gpu.utilization_gpu.toFixed(1)}%`}
percent={gpu.utilizationGpu}
text={`${gpu.utilizationGpu.toFixed(1)}%`}
/>
</Box>
);
@@ -119,8 +119,8 @@ const WorkerGPUEntry: React.FC<WorkerGPUEntryProps> = ({ resourceSlot }) => {
);
};
const WorkerGPU: WorkerFeatureRenderFn = ({ rayletWorker }) => {
const workerRes = rayletWorker?.coreWorkerStats.usedResources;
const WorkerGPU: WorkerFeatureRenderFn = ({ worker }) => {
const workerRes = worker.coreWorkerStats[0].usedResources;
const workerUsedGPUResources = workerRes?.["GPU"];
let message;
if (workerUsedGPUResources === undefined) {
@@ -147,8 +147,8 @@ const WorkerGPU: WorkerFeatureRenderFn = ({ rayletWorker }) => {
return <div style={{ minWidth: 60 }}>{message}</div>;
};
const workerGPUUtilization = (rayletWorker: RayletWorkerStats | null) => {
const workerRes = rayletWorker?.coreWorkerStats.usedResources;
const workerGPUUtilization = (worker: Worker | null) => {
const workerRes = worker?.coreWorkerStats[0].usedResources;
const workerUsedGPUResources = workerRes?.["GPU"];
return (
workerUsedGPUResources &&
@@ -160,8 +160,8 @@ const workerGPUUtilization = (rayletWorker: RayletWorkerStats | null) => {
);
};
const workerGPUAccessor: Accessor<WorkerFeatureData> = ({ rayletWorker }) => {
return workerGPUUtilization(rayletWorker) ?? 0;
const workerGPUAccessor: Accessor<WorkerFeatureData> = ({ worker }) => {
return workerGPUUtilization(worker) ?? 0;
};
const gpuFeature: NodeInfoFeature = {
@@ -19,7 +19,7 @@ import {
const GRAM_COL_WIDTH = 120;
const nodeGRAMUtilization = (node: Node) => {
const utilization = (gpu: GPUStats) => gpu.memory_used / gpu.memory_total;
const utilization = (gpu: GPUStats) => gpu.memoryUsed / gpu.memoryTotal;
if (node.gpus.length === 0) {
return NaN;
}
@@ -69,8 +69,8 @@ export const NodeGRAM: NodeFeatureRenderFn = ({ node }) => {
const nodeGRAMEntries = node.gpus.map((gpu, i) => {
const props = {
gpuName: gpu.name,
utilization: gpu.memory_used,
total: gpu.memory_total,
utilization: gpu.memoryUsed,
total: gpu.memoryTotal,
slot: i,
};
return <GRAMEntry {...props} />;
@@ -124,8 +124,8 @@ export const WorkerGRAM: WorkerFeatureRenderFn = ({ worker, node }) => {
}
const props = {
gpuName: gpu.name,
total: gpu.memory_total,
utilization: process.gpu_memory_usage,
total: gpu.memoryTotal,
utilization: process.gpuMemoryUsage,
slot: i,
};
return <GRAMEntry {...props} />;
@@ -148,7 +148,7 @@ const workerGRAMUtilization = (worker: any, node: Node) => {
processes.find((process) => process.pid === worker.pid),
);
const workerUtilPerGPU = workerProcessPerGPU.map(
(proc) => proc?.gpu_memory_usage || 0,
(proc) => proc?.gpuMemoryUsage || 0,
);
return sum(workerUtilPerGPU);
};
@@ -5,7 +5,6 @@ import { Accessor } from "../../../../common/tableUtils";
import { sum } from "../../../../common/util";
import {
ClusterFeatureRenderFn,
Node,
NodeFeatureData,
NodeFeatureRenderFn,
NodeInfoFeature,
@@ -13,11 +12,8 @@ import {
WorkerFeatureRenderFn,
} from "./types";
const nodeLogCount = (node: Node) =>
node.log_count ? sum(Object.values(node.log_count)) : 0;
const ClusterLogs: ClusterFeatureRenderFn = ({ nodes }) => {
const totalLogCount = sum(nodes.map(nodeLogCount));
const totalLogCount = sum(nodes.map((n) => n.logCount));
return totalLogCount === 0 ? (
<Typography color="textSecondary" component="span" variant="inherit">
No logs
@@ -30,47 +26,41 @@ const ClusterLogs: ClusterFeatureRenderFn = ({ nodes }) => {
};
const makeNodeLogs = (
setLogDialog: (hostname: string, pid: number | null) => void,
): NodeFeatureRenderFn => ({ node }) => {
const logCount = nodeLogCount(node);
return logCount === 0 ? (
setLogDialog: (nodeIp: string, pid: number | null) => void,
): NodeFeatureRenderFn => ({ node }) =>
node.logCount === 0 ? (
<Typography color="textSecondary" component="span" variant="inherit">
No logs
</Typography>
) : (
<SpanButton onClick={() => setLogDialog(node.hostname, null)}>
View all logs ({logCount.toLocaleString()}{" "}
{logCount === 1 ? "line" : "lines"})
<SpanButton onClick={() => setLogDialog(node.ip, null)}>
View all logs ({node.logCount.toLocaleString()}{" "}
{node.logCount === 1 ? "line" : "lines"})
</SpanButton>
);
};
const nodeLogsAccessor: Accessor<NodeFeatureData> = ({ node }) =>
node.log_count ? sum(Object.values(node.log_count)) : 0;
node.logCount ? sum(Object.values(node.logCount)) : 0;
const makeWorkerLogs = (
setLogDialog: (hostname: string, pid: number | null) => void,
): WorkerFeatureRenderFn => ({ node, worker }) => {
const workerLogCount = node.log_count?.[worker.pid] || 0;
return workerLogCount !== 0 ? (
<SpanButton onClick={() => setLogDialog(node.hostname, worker.pid)}>
View log ({workerLogCount.toLocaleString()}{" "}
{workerLogCount === 1 ? "line" : "lines"})
setLogDialog: (nodeIp: string, pid: number | null) => void,
): WorkerFeatureRenderFn => ({ worker, node }) =>
worker.logCount !== 0 ? (
<SpanButton onClick={() => setLogDialog(node.ip, worker.pid)}>
View log ({worker.logCount.toLocaleString()}{" "}
{worker.logCount === 1 ? "line" : "lines"})
</SpanButton>
) : (
<Typography color="textSecondary" component="span" variant="inherit">
No logs
</Typography>
);
};
const workerLogsAccessor: Accessor<WorkerFeatureData> = ({ worker, node }) => {
const workerLogCount = node.log_count?.[worker.pid] || 0;
return workerLogCount;
};
const workerLogsAccessor: Accessor<WorkerFeatureData> = ({ worker }) =>
worker.logCount;
const makeLogsFeature = (
setLogDialog: (hostname: string, pid: number | null) => void,
setLogDialog: (nodeIp: string, pid: number | null) => void,
): NodeInfoFeature => ({
id: "logs",
ClusterFeatureRenderFn: ClusterLogs,
@@ -13,13 +13,11 @@ import {
WorkerFeatureRenderFn,
} from "./types";
export const ClusterObjectStoreMemory: ClusterFeatureRenderFn = ({
plasmaStats,
}) => {
export const ClusterObjectStoreMemory: ClusterFeatureRenderFn = ({ nodes }) => {
const totalAvailable = sum(
plasmaStats.map((s) => s.object_store_available_memory),
nodes.map((n) => n.raylet.objectStoreAvailableMemory),
);
const totalUsed = sum(plasmaStats.map((s) => s.object_store_used_memory));
const totalUsed = sum(nodes.map((n) => n.raylet.objectStoreUsedMemory));
return (
<div style={{ minWidth: 60 }}>
<UsageBar
@@ -30,39 +28,30 @@ export const ClusterObjectStoreMemory: ClusterFeatureRenderFn = ({
);
};
export const NodeObjectStoreMemory: NodeFeatureRenderFn = ({ plasmaStats }) => {
if (!plasmaStats) {
export const NodeObjectStoreMemory: NodeFeatureRenderFn = ({ node }) => {
const total = node.raylet.objectStoreAvailableMemory;
const used = node.raylet.objectStoreUsedMemory;
if (!used || !total) {
return (
<Typography color="textSecondary" component="span" variant="inherit">
N/A
</Typography>
);
}
const {
object_store_used_memory,
object_store_available_memory,
} = plasmaStats;
const usageRatio = object_store_used_memory / object_store_available_memory;
const usageRatio = used / total;
return (
<div style={{ minWidth: 60 }}>
<UsageBar
percent={usageRatio * 100}
text={formatUsage(
object_store_used_memory,
object_store_available_memory,
"mebibyte",
false,
)}
text={formatUsage(used, total, "mebibyte", false)}
/>
</div>
);
};
export const nodeObjectStoreMemoryAccessor: Accessor<NodeFeatureData> = ({
plasmaStats,
}) => {
return plasmaStats?.object_store_used_memory ?? 0;
};
node,
}) => node.raylet.objectStoreUsedMemory;
export const WorkerObjectStoreMemory: WorkerFeatureRenderFn = () => (
<Typography color="textSecondary" component="span" variant="inherit">
@@ -38,13 +38,13 @@ export const nodeRAMAccessor: Accessor<NodeFeatureData> = ({ node }) =>
export const WorkerRAM: WorkerFeatureRenderFn = ({ node, worker }) => (
<UsageBar
percent={(100 * worker.memory_info.rss) / node.mem[0]}
text={formatByteAmount(worker.memory_info.rss, "mebibyte")}
percent={(100 * worker.memoryInfo.rss) / node.mem[0]}
text={formatByteAmount(worker.memoryInfo.rss, "mebibyte")}
/>
);
export const workerRAMAccessor: Accessor<WorkerFeatureData> = ({ worker }) =>
worker.memory_info.rss;
worker.memoryInfo.rss;
const ramFeature: NodeInfoFeature = {
id: "ram",
@@ -20,20 +20,20 @@ export const ClusterUptime: ClusterFeatureRenderFn = ({ nodes }) => (
);
export const NodeUptime: NodeFeatureRenderFn = ({ node }) => (
<React.Fragment>{formatDuration(getUptime(node.boot_time))}</React.Fragment>
<React.Fragment>{formatDuration(getUptime(node.bootTime))}</React.Fragment>
);
export const nodeUptimeAccessor: Accessor<NodeFeatureData> = ({ node }) =>
getUptime(node.boot_time);
getUptime(node.bootTime);
export const WorkerUptime: WorkerFeatureRenderFn = ({ worker }) => (
<React.Fragment>
{formatDuration(getUptime(worker.create_time))}
{formatDuration(getUptime(worker.createTime))}
</React.Fragment>
);
const workerUptimeAccessor: Accessor<WorkerFeatureData> = ({ worker }) =>
getUptime(worker.create_time);
getUptime(worker.createTime);
const uptimeFeature: NodeInfoFeature = {
id: "uptime",
@@ -1,21 +1,16 @@
import React from "react";
import {
NodeInfoResponse,
PlasmaStats,
RayletWorkerStats,
} from "../../../../api";
import { NodeInfoResponse } from "../../../../api";
import { Accessor } from "../../../../common/tableUtils";
type ArrayType<T> = T extends Array<infer U> ? U : never;
export type Node = ArrayType<NodeInfoResponse["clients"]>;
export type Worker = ArrayType<Node["workers"]>;
type ClusterFeatureData = { nodes: Node[]; plasmaStats: PlasmaStats[] };
export type NodeFeatureData = { node: Node; plasmaStats?: PlasmaStats };
type ClusterFeatureData = { nodes: Node[] };
export type NodeFeatureData = { node: Node };
export type WorkerFeatureData = {
node: Node;
worker: Worker;
rayletWorker: RayletWorkerStats | null;
};
export type ClusterFeatureRenderFn = (
@@ -1,5 +1,6 @@
import {
createStyles,
makeStyles,
Table,
TableBody,
TableCell,
@@ -7,17 +8,15 @@ import {
TableRow,
Theme,
Typography,
withStyles,
WithStyles,
} from "@material-ui/core";
import classNames from "classnames";
import React from "react";
import { connect } from "react-redux";
import React, { useCallback, useEffect, useRef } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getRayConfig } from "../../../api";
import { StoreState } from "../../../store";
import { dashboardActions } from "../state";
const styles = (theme: Theme) =>
const useRayConfigStyles = makeStyles((theme: Theme) =>
createStyles({
table: {
marginTop: theme.spacing(1),
@@ -36,104 +35,98 @@ const styles = (theme: Theme) =>
key: {
color: theme.palette.text.secondary,
},
});
}),
);
const { setRayConfig, setError } = dashboardActions;
const configSelector = (state: StoreState) => state.dashboard.rayConfig;
const mapStateToProps = (state: StoreState) => ({
rayConfig: state.dashboard.rayConfig,
});
const mapDispatchToProps = dashboardActions;
class RayConfig extends React.Component<
WithStyles<typeof styles> &
ReturnType<typeof mapStateToProps> &
typeof mapDispatchToProps
> {
refreshRayConfig = async () => {
const RayConfig: React.FC = () => {
const classes = useRayConfigStyles();
const rayConfig = useSelector(configSelector);
const dispatch = useDispatch();
const refreshData = useCallback(async () => {
try {
const rayConfig = await getRayConfig();
this.props.setRayConfig(rayConfig);
dispatch(setRayConfig(rayConfig));
} catch (error) {
} finally {
setTimeout(this.refreshRayConfig, 10 * 1000);
dispatch(setError(error.toString()));
}
};
async componentDidMount() {
await this.refreshRayConfig();
}
render() {
const { classes, rayConfig } = this.props;
if (rayConfig === null) {
return (
<Typography color="textSecondary">
No Ray configuration detected.
</Typography>
);
}, [dispatch]);
const intervalId = useRef<any>(null);
useEffect(() => {
if (intervalId.current === null) {
refreshData();
intervalId.current = setInterval(refreshData, 10000);
}
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"
}`,
},
];
const cleanup = () => {
clearInterval(intervalId.current);
};
return cleanup;
}, [refreshData]);
if (rayConfig === null) {
return (
<div>
<Typography>Ray cluster configuration:</Typography>
<Table className={classes.table}>
<TableHead>
<TableRow>
<TableCell className={classes.cell}>Setting</TableCell>
<TableCell className={classes.cell}>Value</TableCell>
</TableRow>
</TableHead>
<TableBody>
{formattedRayConfig.map(({ key, value }, index) => (
<TableRow key={index}>
<TableCell className={classNames(classes.cell, classes.key)}>
{key}
</TableCell>
<TableCell className={classes.cell}>{value}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<Typography color="textSecondary">
No Ray configuration detected.
</Typography>
);
}
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(withStyles(styles)(RayConfig));
const formattedRayConfig = [
{
key: "Autoscaling mode",
value: rayConfig.autoscalingMode,
},
{
key: "Head node type",
value: rayConfig.headType,
},
{
key: "Worker node type",
value: rayConfig.workerType,
},
{
key: "Min worker nodes",
value: rayConfig.minWorkers,
},
{
key: "Initial worker nodes",
value: rayConfig.initialWorkers,
},
{
key: "Max worker nodes",
value: rayConfig.maxWorkers,
},
{
key: "Idle timeout",
value: `${rayConfig.idleTimeoutMinutes} ${
rayConfig.idleTimeoutMinutes === 1 ? "minute" : "minutes"
}`,
},
];
return (
<div>
<Typography>Ray cluster configuration:</Typography>
<Table className={classes.table}>
<TableHead>
<TableRow>
<TableCell className={classes.cell}>Setting</TableCell>
<TableCell className={classes.cell}>Value</TableCell>
</TableRow>
</TableHead>
<TableBody>
{formattedRayConfig.map(({ key, value }, index) => (
<TableRow key={index}>
<TableCell className={classNames(classes.cell, classes.key)}>
{key}
</TableCell>
<TableCell className={classes.cell}>{value}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
);
};
export default RayConfig;
+19 -71
View File
@@ -1,13 +1,16 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import {
ActorGroup,
ActorsResponse,
MemoryTable,
MemoryTableResponse,
NodeInfoResponse,
RayConfigResponse,
RayletInfoResponse,
TuneAvailability,
TuneAvailabilityResponse,
TuneJob,
TuneJobResponse,
} from "../../api";
import { filterObj } from "../../common/util";
const name = "dashboard";
@@ -15,20 +18,20 @@ type State = {
tab: number;
rayConfig: RayConfigResponse | null;
nodeInfo: NodeInfoResponse | null;
rayletInfo: RayletInfoResponse | null;
tuneInfo: TuneJobResponse | null;
tuneAvailability: TuneAvailabilityResponse | null;
actorGroups: { [key: string]: ActorGroup } | null;
tuneInfo: TuneJob | null;
tuneAvailability: TuneAvailability | null;
lastUpdatedAt: number | null;
error: string | null;
memoryTable: MemoryTableResponse | null;
memoryTable: MemoryTable | null;
shouldObtainMemoryTable: boolean;
};
const initialState: State = {
actorGroups: null,
tab: 0,
rayConfig: null,
nodeInfo: null,
rayletInfo: null,
tuneInfo: null,
tuneAvailability: null,
lastUpdatedAt: null,
@@ -47,39 +50,34 @@ const slice = createSlice({
setRayConfig: (state, action: PayloadAction<RayConfigResponse>) => {
state.rayConfig = action.payload;
},
setNodeAndRayletInfo: (
setNodeInfo: (
state,
action: PayloadAction<{
nodeInfo: NodeInfoResponse;
rayletInfo: RayletInfoResponse;
}>,
) => {
state.rayletInfo = action.payload.rayletInfo;
state.nodeInfo = filterNonClusterWorkerInfo(
action.payload.rayletInfo,
action.payload.nodeInfo,
);
state.nodeInfo = action.payload.nodeInfo;
state.lastUpdatedAt = Date.now();
},
setActorGroups: (state, action: PayloadAction<ActorsResponse>) => {
state.actorGroups = action.payload.actorGroups;
},
setTuneInfo: (state, action: PayloadAction<TuneJobResponse>) => {
state.tuneInfo = action.payload;
state.tuneInfo = action.payload.result;
state.lastUpdatedAt = Date.now();
},
setTuneAvailability: (
state,
action: PayloadAction<TuneAvailabilityResponse>,
) => {
state.tuneAvailability = action.payload;
state.tuneAvailability = action.payload.result;
state.lastUpdatedAt = Date.now();
},
setError: (state, action: PayloadAction<string | null>) => {
state.error = action.payload;
},
setMemoryTable: (
state,
action: PayloadAction<MemoryTableResponse | null>,
) => {
state.memoryTable = action.payload;
setMemoryTable: (state, action: PayloadAction<MemoryTableResponse>) => {
state.memoryTable = action.payload.memoryTable;
},
setShouldObtainMemoryTable: (state, action: PayloadAction<boolean>) => {
state.shouldObtainMemoryTable = action.payload;
@@ -87,55 +85,5 @@ const slice = createSlice({
},
});
const clusterWorkerPids = (
rayletInfo: RayletInfoResponse,
): Map<string, Set<number>> => {
// Groups PIDs registered with the raylet by node IP address
// This is used to filter out processes belonging to other ray clusters.
const nodeMap = new Map();
const workerPids = new Set();
for (const [nodeIp, { workersStats }] of Object.entries(rayletInfo.nodes)) {
for (const worker of workersStats) {
if (!worker.isDriver) {
workerPids.add(worker.pid);
}
}
nodeMap.set(nodeIp, workerPids);
}
return nodeMap;
};
const filterNonClusterWorkerInfo = (
rayletInfo: RayletInfoResponse,
nodeInfo: NodeInfoResponse,
) => {
// The back-end that generates the NodeInfoResponse does not remove worker
// information of workers that belong to other clusters, so we do it here.
const workerPidsByIP = clusterWorkerPids(rayletInfo);
const filteredClients = nodeInfo.clients.map((client) => {
const workerPids = workerPidsByIP.get(client.ip);
const workers = client.workers.filter((worker) =>
workerPids?.has(worker.pid),
);
const logs = client.log_count
? filterObj(client.log_count, ([pid, _]: [string, any]) =>
workerPids?.has(parseInt(pid)),
)
: {};
const errors = client.error_count
? filterObj(client.error_count, ([pid, _]: [string, any]) =>
workerPids?.has(parseInt(pid)),
)
: {};
client.workers = workers;
client.log_count = logs;
client.error_count = errors;
return client;
});
return {
clients: filteredClients,
};
};
export const dashboardActions = slice.actions;
export const dashboardReducer = slice.reducer;
@@ -177,7 +177,7 @@ class Tune extends React.Component<
render() {
const { classes, tuneInfo, tuneAvailability } = this.props;
if (tuneAvailability && !tuneAvailability.trials_available) {
if (tuneAvailability && !tuneAvailability.trialsAvailable) {
return this.experimentChoice(true);
}
@@ -90,10 +90,10 @@ class TuneErrors extends React.Component<
Object.keys(tuneInfo.errors).map((key, index) => (
<TableRow key={index}>
<TableCell className={classes.cell}>
{tuneInfo.errors[key].job_id}
{tuneInfo.errors[key].jobId}
</TableCell>
<TableCell className={classes.cell}>
{tuneInfo.errors[key].trial_id}
{tuneInfo.errors[key].trialId}
</TableCell>
<TableCell className={classes.cell}>{key}</TableCell>
<TableCell className={classes.cell}>
@@ -184,11 +184,11 @@ class TuneTable extends React.Component<
const { tuneInfo } = this.props;
const { sortedColumn, ascending, metricParamColumn } = this.state;
if (tuneInfo === null || Object.keys(tuneInfo.trial_records).length === 0) {
if (tuneInfo === null || Object.keys(tuneInfo.trialRecords).length === 0) {
return null;
}
const trialDetails = Object.values(tuneInfo.trial_records);
const trialDetails = Object.values(tuneInfo.trialRecords);
if (!sortedColumn) {
return trialDetails;
@@ -304,15 +304,12 @@ class TuneTable extends React.Component<
const { metricColumns, paramColumns, open, errorTrial } = this.state;
if (
tuneInfo === null ||
Object.keys(tuneInfo["trial_records"]).length === 0
) {
if (tuneInfo === null || Object.keys(tuneInfo.trialRecords).length === 0) {
return null;
}
const firstTrial = Object.keys(tuneInfo.trial_records)[0];
const paramsDict = tuneInfo.trial_records[firstTrial].params;
const firstTrial = Object.keys(tuneInfo.trialRecords)[0];
const paramsDict = tuneInfo.trialRecords[firstTrial].params;
const paramNames = Object.keys(paramsDict).filter((k) => k !== "args");
let viewableParams = paramNames;
@@ -326,7 +323,7 @@ class TuneTable extends React.Component<
viewableParams = paramColumns;
}
const metricNames = Object.keys(tuneInfo.trial_records[firstTrial].metrics);
const metricNames = Object.keys(tuneInfo.trialRecords[firstTrial].metrics);
let viewableMetrics = metricNames;
const metricOptions = metricNames.length > 3;
@@ -358,9 +355,9 @@ class TuneTable extends React.Component<
<Table stickyHeader>
<TableHead>
<TableRow>
{this.sortedCell("trial_id")}
{this.sortedCell("job_id")}
{this.sortedCell("start_time")}
{this.sortedCell("trialId")}
{this.sortedCell("jobId")}
{this.sortedCell("startTime")}
{viewableParams.map((value, index) =>
this.sortedCell("params", value, index),
)}
@@ -378,30 +375,30 @@ class TuneTable extends React.Component<
trialDetails.map((trial, index) => (
<TableRow key={index}>
<TableCell className={classes.cell}>
{trial["trial_id"]}
{trial.trialId}
</TableCell>
<TableCell className={classes.cell}>
{trial["job_id"]}
{trial.jobId}
</TableCell>
<TableCell className={classes.cell}>
{trial["start_time"]}
{trial.startTime}
</TableCell>
{viewableParams.map((value, index) => (
<TableCell className={classes.cell} key={index}>
{typeof trial["params"][value] === "number"
? formatValue(Number(trial["params"][value]))
: trial["params"][value]}
{typeof trial.params[value] === "number"
? formatValue(Number(trial.params[value]))
: trial.params[value]}
</TableCell>
))}
<TableCell className={classes.cell}>
{trial["status"]}
</TableCell>
{trial["metrics"] &&
{trial.metrics &&
viewableMetrics.map((value, index) => (
<TableCell className={classes.cell} key={index}>
{typeof trial["metrics"][value] === "number"
? formatValue(Number(trial["metrics"][value]))
: trial["metrics"][value]}
{typeof trial.metrics[value] === "number"
? formatValue(Number(trial.metrics[value]))
: trial.metrics[value]}
</TableCell>
))}
<TableCell className={classes.cell}>
@@ -412,7 +409,7 @@ class TuneTable extends React.Component<
component="button"
variant="body2"
onClick={() => {
this.handleOpen(trial["trial_id"]);
this.handleOpen(trial.trialId);
}}
>
Show Error
@@ -429,7 +426,7 @@ class TuneTable extends React.Component<
<DialogWithTitle handleClose={this.handleClose} title="Error Log">
{open && (
<NumberedLines
lines={tuneInfo.trial_records[errorTrial].error
lines={tuneInfo.trialRecords[errorTrial].error
.trim()
.split("\n")}
/>
@@ -84,7 +84,7 @@ class TuneTensorBoard extends React.Component<
"tensorboard --logdir" if not displaying below.
</Typography>
)}
{tuneInfo && !tuneInfo.tensorboard.tensorboard_current && (
{tuneInfo && !tuneInfo.tensorboard.tensorboardCurrent && (
<Typography className={classes.warning} color="textSecondary">
The below Tensorboard reflects a previously entered log directory.
Restart the Ray Dashboard to change the Tensorboard logdir.
@@ -107,7 +107,7 @@ class TuneTensorBoard extends React.Component<
if (tuneInfo === null) {
return;
}
const enabled = tuneInfo.tensorboard.tensorboard_enabled;
const enabled = tuneInfo.tensorboard.tensorboardEnabled;
return (
<div className={classes.root}>
{!enabled && (