Skip to content

Commit

Permalink
Merge pull request #6 from anyscale/hdash-grafana
Browse files Browse the repository at this point in the history
Grafana Integration
  • Loading branch information
rkooo567 authored Mar 4, 2020
2 parents ece32f3 + 08527d4 commit 2ec86df
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 30 deletions.
15 changes: 13 additions & 2 deletions python/ray/dashboard/client/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const base =
process.env.NODE_ENV === "development"
? "http://localhost:8265"
? "http://localhost:8266"
: window.location.origin;

// TODO(mitchellstern): Add JSON schema validation for the responses.
Expand Down Expand Up @@ -244,4 +244,15 @@ export interface IsHostedResponse {
}

export const getIsHosted = () =>
get<IsHostedResponse>("/api/is_hosted", {});
get<IsHostedResponse>("/api/is_hosted", {});

export interface GetGrafanaIframeResponse {
frame_html: string
}
export const getGrafanaIframe = (
pid: number | "All",
metric: "cpu" | "memory"
) => get<GetGrafanaIframeResponse>("/api/grafana_iframe", {
pid: pid,
metric: metric
})
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { connect } from "react-redux";
import { StoreState } from "../../../store";
import Errors from "./dialogs/errors/Errors";
import Logs from "./dialogs/logs/Logs";
import IFrame from "./dialogs/grafana/IFrame";
import NodeRowGroup from "./NodeRowGroup";
import TotalRow from "./TotalRow";

Expand All @@ -37,14 +38,16 @@ const mapStateToProps = (state: StoreState) => ({
interface State {
logDialog: { hostname: string; pid: number | null } | null;
errorDialog: { hostname: string; pid: number | null } | null;
iframeDialog: { pid: number | "All"; metric: "cpu" | "memory" } | null;
}

class NodeInfo extends React.Component<
WithStyles<typeof styles> & ReturnType<typeof mapStateToProps>
> {
state: State = {
logDialog: null,
errorDialog: null
errorDialog: null,
iframeDialog: null
};

setLogDialog = (hostname: string, pid: number | null) => {
Expand All @@ -63,9 +66,17 @@ class NodeInfo extends React.Component<
this.setState({ errorDialog: null });
};

setIframeDialog = (pid: number | "All", metric: "cpu" | "memory") => {
this.setState({ iframeDialog: { pid, metric } });
};

clearIframeDialog = () => {
this.setState({ iframeDialog: null });
};

render() {
const { classes, nodeInfo, rayletInfo } = this.props;
const { logDialog, errorDialog } = this.state;
const { logDialog, errorDialog, iframeDialog } = this.state;

if (nodeInfo === null || rayletInfo === null) {
return <Typography color="textSecondary">Loading...</Typography>;
Expand Down Expand Up @@ -148,6 +159,7 @@ class NodeInfo extends React.Component<
errorCounts={errorCounts[client.ip]}
setLogDialog={this.setLogDialog}
setErrorDialog={this.setErrorDialog}
setIframeDialog={this.setIframeDialog}
initialExpanded={nodeInfo.clients.length <= 1}
/>
))}
Expand All @@ -172,6 +184,13 @@ class NodeInfo extends React.Component<
pid={errorDialog.pid}
/>
)}
{iframeDialog !== null && (
<IFrame
clearDialog={this.clearIframeDialog}
pid={iframeDialog.pid}
metric={iframeDialog.metric}
/>
)}
</React.Fragment>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import RemoveIcon from "@material-ui/icons/Remove";
import classNames from "classnames";
import React from "react";
import { NodeInfoResponse, RayletInfoResponse } from "../../../api";
import { NodeCPU, WorkerCPU } from "./features/CPU";
import { makeNodeCPU, makeWorkerCPU } from "./features/CPU";
import { NodeDisk, WorkerDisk } from "./features/Disk";
import { makeNodeErrors, makeWorkerErrors } from "./features/Errors";
import { NodeHost, WorkerHost } from "./features/Host";
import { makeNodeLogs, makeWorkerLogs } from "./features/Logs";
import { NodeRAM, WorkerRAM } from "./features/RAM";
import { makeNodeRAM, makeWorkerRAM } from "./features/RAM";
import { NodeReceived, WorkerReceived } from "./features/Received";
import { NodeSent, WorkerSent } from "./features/Sent";
import { NodeUptime, WorkerUptime } from "./features/Uptime";
Expand Down Expand Up @@ -58,6 +58,7 @@ interface Props {
};
setLogDialog: (hostname: string, pid: number | null) => void;
setErrorDialog: (hostname: string, pid: number | null) => void;
setIframeDialog: (pid: number | "All", metric: "cpu" | "memory") => void;
initialExpanded: boolean;
}

Expand Down Expand Up @@ -87,16 +88,23 @@ class NodeRowGroup extends React.Component<
logCounts,
errorCounts,
setLogDialog,
setErrorDialog
setErrorDialog,
setIframeDialog
} = this.props;
const { expanded } = this.state;

const features = [
{ NodeFeature: NodeHost, WorkerFeature: WorkerHost },
{ NodeFeature: NodeWorkers, WorkerFeature: WorkerWorkers },
{ NodeFeature: NodeUptime, WorkerFeature: WorkerUptime },
{ NodeFeature: NodeCPU, WorkerFeature: WorkerCPU },
{ NodeFeature: NodeRAM, WorkerFeature: WorkerRAM },
{
NodeFeature: makeNodeCPU(setIframeDialog),
WorkerFeature: makeWorkerCPU(setIframeDialog)
},
{
NodeFeature: makeNodeRAM(setIframeDialog),
WorkerFeature: makeWorkerRAM(setIframeDialog)
},
{ NodeFeature: NodeDisk, WorkerFeature: WorkerDisk },
{ NodeFeature: NodeSent, WorkerFeature: WorkerSent },
{ NodeFeature: NodeReceived, WorkerFeature: WorkerReceived },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { fade } from "@material-ui/core/styles/colorManipulator";
import { Theme } from "@material-ui/core/styles/createMuiTheme";
import createStyles from "@material-ui/core/styles/createStyles";
import withStyles, { WithStyles } from "@material-ui/core/styles/withStyles";
import Typography from "@material-ui/core/Typography";
import React from "react";
import { getGrafanaIframe, GetGrafanaIframeResponse } from "../../../../../api";
import DialogWithTitle from "../../../../../common/DialogWithTitle";

const styles = (theme: Theme) =>
createStyles({
header: {
lineHeight: 1,
marginBottom: theme.spacing(3),
marginTop: theme.spacing(3)
}
});

interface Props {
clearDialog: () => void;
pid: number | "All";
metric: "cpu" | "memory";
}

interface State {
result: GetGrafanaIframeResponse | null;
error: string | null;
}

class IFrame extends React.Component<Props & WithStyles<typeof styles>, State> {
state: State = {
result: null,
error: null
};

async componentDidMount() {
try {
const { metric, pid } = this.props;
const result = await getGrafanaIframe(pid, metric);
this.setState({ result, error: null });
} catch (error) {
this.setState({ result: null, error: error.toString() });
}
}

render() {
const { classes, clearDialog } = this.props;
const { result, error } = this.state;

return (
<DialogWithTitle handleClose={clearDialog} title="Historical Metrics">
{error !== null ? (
<Typography color="error">{error}</Typography>
) : result === null ? (
<Typography color="textSecondary">Loading...</Typography>
) : (
<div dangerouslySetInnerHTML={{ __html: result.frame_html }} />
)}
</DialogWithTitle>
);
}
}

export default withStyles(styles)(IFrame);
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react";
import UsageBar from "../../../../common/UsageBar";
import SpanButton from "../../../../common/SpanButton";
import {
ClusterFeatureComponent,
NodeFeatureComponent,
Expand Down Expand Up @@ -39,17 +40,30 @@ export const ClusterCPU: ClusterFeatureComponent = ({ nodes }) => {
);
};

export const NodeCPU: NodeFeatureComponent = ({ node }) => (
<div style={{ minWidth: 60 }}>
<UsageBar percent={node.cpu} text={`${node.cpu.toFixed(1)}%`} />
</div>
type setIframeDialogType = (
pid: number | "All",
metric: "cpu" | "memory"
) => void;

export const makeNodeCPU = (
setIframeDialog: setIframeDialogType
): NodeFeatureComponent => ({ node }) => (
<SpanButton onClick={() => setIframeDialog("All", "cpu")}>
<div style={{ minWidth: 60 }}>
<UsageBar percent={node.cpu} text={`${node.cpu.toFixed(1)}%`} />
</div>
</SpanButton>
);

export const WorkerCPU: WorkerFeatureComponent = ({ worker }) => (
<div style={{ minWidth: 60 }}>
<UsageBar
percent={worker.cpu_percent}
text={`${worker.cpu_percent.toFixed(1)}%`}
/>
</div>
export const makeWorkerCPU = (
setIframeDialog: setIframeDialogType
): WorkerFeatureComponent => ({ worker }) => (
<SpanButton onClick={() => setIframeDialog(worker.pid, "cpu")}>
<div style={{ minWidth: 60 }}>
<UsageBar
percent={worker.cpu_percent}
text={`${worker.cpu_percent.toFixed(1)}%`}
/>
</div>
</SpanButton>
);
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
NodeFeatureComponent,
WorkerFeatureComponent
} from "./types";
import SpanButton from "../../../../common/SpanButton";

export const ClusterRAM: ClusterFeatureComponent = ({ nodes }) => {
let used = 0;
Expand All @@ -22,16 +23,29 @@ export const ClusterRAM: ClusterFeatureComponent = ({ nodes }) => {
);
};

export const NodeRAM: NodeFeatureComponent = ({ node }) => (
<UsageBar
percent={(100 * (node.mem[0] - node.mem[1])) / node.mem[0]}
text={formatUsage(node.mem[0] - node.mem[1], node.mem[0], "gibibyte")}
/>
type setIframeDialogType = (
pid: number | "All",
metric: "cpu" | "memory"
) => void;

export const makeNodeRAM = (
setIframeDialog: setIframeDialogType
): NodeFeatureComponent => ({ node }) => (
<SpanButton onClick={() => setIframeDialog("All", "memory")}>
<UsageBar
percent={(100 * (node.mem[0] - node.mem[1])) / node.mem[0]}
text={formatUsage(node.mem[0] - node.mem[1], node.mem[0], "gibibyte")}
/>
</SpanButton>
);

export const WorkerRAM: WorkerFeatureComponent = ({ node, worker }) => (
<UsageBar
percent={(100 * worker.memory_info.rss) / node.mem[0]}
text={formatByteAmount(worker.memory_info.rss, "mebibyte")}
/>
export const makeWorkerRAM = (
setIframeDialog: setIframeDialogType
): WorkerFeatureComponent => ({ node, worker }) => (
<SpanButton onClick={() => setIframeDialog(worker.pid, "memory")}>
<UsageBar
percent={(100 * worker.memory_info.rss) / node.mem[0]}
text={formatByteAmount(worker.memory_info.rss, "mebibyte")}
/>
</SpanButton>
);
12 changes: 12 additions & 0 deletions python/ray/dashboard/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,17 @@ async def to_hosted_redirect(req) -> aiohttp.web.Response:
dashboard_url = self.start_exporter()
raise aiohttp.web.HTTPFound(dashboard_url)

async def grafana_iframe(req) -> aiohttp.web.Response:
if not self.dashboard_controller.is_hosted:
return get_forbidden(req)

iframe_div = self.dashboard_controller.get_grafana_iframe({
"pid": req.query.get("pid"),
"metric": req.query.get("metric")
})
return await json_response({"frame_html": iframe_div})


self.app.router.add_get("/", get_index)
self.app.router.add_get("/favicon.ico", get_favicon)

Expand Down Expand Up @@ -468,6 +479,7 @@ async def to_hosted_redirect(req) -> aiohttp.web.Response:
self.app.router.add_get('/api/is_hosted', is_hosted)
self.app.router.add_get("/to_hosted", to_hosted)
self.app.router.add_get("/to_hosted_agreed", to_hosted_redirect)
self.app.router.add_get("/api/grafana_iframe", grafana_iframe)

self.app.router.add_get("/{_}", get_forbidden)

Expand Down

0 comments on commit 2ec86df

Please sign in to comment.