mirror of
https://github.com/wassname/ray.git
synced 2026-09-09 11:32:43 +08:00
[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:
+225
-185
@@ -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;
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
Reference in New Issue
Block a user