diff --git a/dashboard/client/src/pages/metrics/Metrics.tsx b/dashboard/client/src/pages/metrics/Metrics.tsx index 15c501aec610..5b9d8ef08000 100644 --- a/dashboard/client/src/pages/metrics/Metrics.tsx +++ b/dashboard/client/src/pages/metrics/Metrics.tsx @@ -2,6 +2,7 @@ import { Alert, AlertProps, Button, + InputAdornment, Link, Menu, MenuItem, @@ -12,11 +13,13 @@ import { import createStyles from "@mui/styles/createStyles"; import makeStyles from "@mui/styles/makeStyles"; import React, { useContext, useEffect, useState } from "react"; +import { BiRefresh, BiTime } from "react-icons/bi"; import { RiExternalLinkLine } from "react-icons/ri"; import { GlobalContext } from "../../App"; import { CollapsibleSection } from "../../common/CollapsibleSection"; import { ClassNameProps } from "../../common/props"; +import { HelpInfo } from "../../components/Tooltip"; import { MainNavPageInfo } from "../layout/mainNavContext"; import { MAIN_NAV_HEIGHT } from "../layout/MainNavLayout"; @@ -45,6 +48,20 @@ const useStyles = makeStyles((theme) => }), ); +export enum RefreshOptions { + OFF = "off", + FIVE_SECONDS = "5s", + TEN_SECONDS = "10s", + THIRTY_SECONDS = "30s", + ONE_MIN = "1m", + FIVE_MINS = "5m", + FIFTEEN_MINS = "15m", + THIRTY_MINS = "30m", + ONE_HOUR = "1h", + TWO_HOURS = "2h", + ONE_DAY = "1d", +} + export enum TimeRangeOptions { FIVE_MINS = "Last 5 minutes", THIRTY_MINS = "Last 30 minutes", @@ -57,6 +74,20 @@ export enum TimeRangeOptions { SEVEN_DAYS = "Last 7 days", } +export const REFRESH_VALUE: Record = { + [RefreshOptions.OFF]: "", + [RefreshOptions.FIVE_SECONDS]: "5s", + [RefreshOptions.TEN_SECONDS]: "10s", + [RefreshOptions.THIRTY_SECONDS]: "30s", + [RefreshOptions.ONE_MIN]: "1m", + [RefreshOptions.FIVE_MINS]: "5m", + [RefreshOptions.FIFTEEN_MINS]: "15m", + [RefreshOptions.THIRTY_MINS]: "30m", + [RefreshOptions.ONE_HOUR]: "1h", + [RefreshOptions.TWO_HOURS]: "2h", + [RefreshOptions.ONE_DAY]: "1d", +}; + export const TIME_RANGE_TO_FROM_VALUE: Record = { [TimeRangeOptions.FIVE_MINS]: "now-5m", [TimeRangeOptions.THIRTY_MINS]: "now-30m", @@ -358,13 +389,25 @@ export const Metrics = () => { const grafanaDefaultDatasource = dashboardDatasource ?? "Prometheus"; + const [refreshOption, setRefreshOption] = useState( + RefreshOptions.FIVE_SECONDS, + ); + const [timeRangeOption, setTimeRangeOption] = useState( TimeRangeOptions.FIVE_MINS, ); + + const [refresh, setRefresh] = useState(null); + const [[from, to], setTimeRange] = useState<[string | null, string | null]>([ null, null, ]); + + useEffect(() => { + setRefresh(REFRESH_VALUE[refreshOption]); + }, [refreshOption]); + useEffect(() => { const from = TIME_RANGE_TO_FROM_VALUE[timeRangeOption]; setTimeRange([from, "now"]); @@ -377,6 +420,8 @@ export const Metrics = () => { const toParam = to !== null ? `&to=${to}` : ""; const timeRangeParams = `${fromParam}${toParam}`; + const refreshParams = refresh ? `&refresh=${refresh}` : ""; + return (
{ className={classes.timeRangeButton} select size="small" - style={{ width: 120 }} + sx={{ width: 80 }} + value={refreshOption} + onChange={({ target: { value } }) => { + setRefreshOption(value as RefreshOptions); + }} + variant="standard" + InputProps={{ + startAdornment: ( + + + + ), + }} + > + {Object.entries(RefreshOptions).map(([key, value]) => ( + + {value} + + ))} + + Auto-refresh interval + { setTimeRangeOption(value as TimeRangeOptions); }} variant="standard" + InputProps={{ + startAdornment: ( + + + + ), + }} > {Object.entries(TimeRangeOptions).map(([key, value]) => ( @@ -448,6 +525,7 @@ export const Metrics = () => { ))} + Time range picker Tip: You can click on the legend to focus on a specific line in the @@ -459,6 +537,7 @@ export const Metrics = () => { { type MetricsSectionProps = { metricConfig: MetricsSectionConfig; + refreshParams: string; timeRangeParams: string; dashboardUid: string; dashboardDatasource: string; @@ -518,6 +599,7 @@ type MetricsSectionProps = { const MetricsSection = ({ metricConfig: { title, contents }, + refreshParams, timeRangeParams, dashboardUid, dashboardDatasource, @@ -538,7 +620,7 @@ const MetricsSection = ({ {contents.map(({ title, pathParams }) => { const path = `/d-solo/${dashboardUid}?${pathParams}` + - `&refresh${timeRangeParams}&var-SessionName=${sessionName}&var-datasource=${dashboardDatasource}`; + `&${refreshParams}${timeRangeParams}&var-SessionName=${sessionName}&var-datasource=${dashboardDatasource}`; return ( ( + RefreshOptions.FIVE_SECONDS, + ); + const [timeRangeOption, setTimeRangeOption] = useState( TimeRangeOptions.FIVE_MINS, ); + + const [refresh, setRefresh] = useState(null); + const [[from, to], setTimeRange] = useState<[string | null, string | null]>([ null, null, ]); + useEffect(() => { + setRefresh(REFRESH_VALUE[refreshOption]); + }, [refreshOption]); useEffect(() => { const from = TIME_RANGE_TO_FROM_VALUE[timeRangeOption]; setTimeRange([from, "now"]); @@ -101,6 +122,7 @@ export const ServeReplicaMetricsSection = ({ const fromParam = from !== null ? `&from=${from}` : ""; const toParam = to !== null ? `&to=${to}` : ""; const timeRangeParams = `${fromParam}${toParam}`; + const refreshParams = refresh ? `&refresh=${refresh}` : ""; const replicaButtonUrl = useViewServeDeploymentMetricsButtonUrl( deploymentName, @@ -125,11 +147,44 @@ export const ServeReplicaMetricsSection = ({ className={classes.timeRangeButton} select size="small" - style={{ width: 120 }} + sx={{ width: 80 }} + value={refreshOption} + onChange={({ target: { value } }) => { + setRefreshOption(value as RefreshOptions); + }} + variant="standard" + InputProps={{ + startAdornment: ( + + + + ), + }} + > + {Object.entries(RefreshOptions).map(([key, value]) => ( + + {value} + + ))} + + Auto-refresh interval + { setTimeRangeOption(value as TimeRangeOptions); }} + variant="standard" + InputProps={{ + startAdornment: ( + + + + ), + }} > {Object.entries(TimeRangeOptions).map(([key, value]) => ( @@ -137,12 +192,13 @@ export const ServeReplicaMetricsSection = ({ ))} + Time range picker
{METRICS_CONFIG.map(({ title, pathParams }) => { const path = `/d-solo/${grafanaServeDashboardUid}?${pathParams}` + - `&refresh${timeRangeParams}&var-Deployment=${encodeURIComponent( + `${refreshParams}${timeRangeParams}&var-Deployment=${encodeURIComponent( deploymentName, )}&var-Replica=${encodeURIComponent( replicaId, diff --git a/dashboard/client/src/pages/serve/ServeMetricsSection.tsx b/dashboard/client/src/pages/serve/ServeMetricsSection.tsx index 953580ec548d..ad3e52a67f24 100644 --- a/dashboard/client/src/pages/serve/ServeMetricsSection.tsx +++ b/dashboard/client/src/pages/serve/ServeMetricsSection.tsx @@ -1,13 +1,24 @@ -import { Box, Button, MenuItem, Paper, TextField } from "@mui/material"; +import { + Box, + Button, + InputAdornment, + MenuItem, + Paper, + TextField, +} from "@mui/material"; import createStyles from "@mui/styles/createStyles"; import makeStyles from "@mui/styles/makeStyles"; import React, { useContext, useEffect, useState } from "react"; +import { BiRefresh, BiTime } from "react-icons/bi"; import { RiExternalLinkLine } from "react-icons/ri"; import { GlobalContext } from "../../App"; import { CollapsibleSection } from "../../common/CollapsibleSection"; import { ClassNameProps } from "../../common/props"; +import { HelpInfo } from "../../components/Tooltip"; import { MetricConfig, + REFRESH_VALUE, + RefreshOptions, TIME_RANGE_TO_FROM_VALUE, TimeRangeOptions, } from "../metrics"; @@ -110,14 +121,20 @@ export const ServeMetricsSection = ({ const { grafanaHost, prometheusHealth, dashboardUids, dashboardDatasource } = useContext(GlobalContext); const grafanaServeDashboardUid = dashboardUids?.serve ?? "rayServeDashboard"; - + const [refreshOption, setRefreshOption] = useState( + RefreshOptions.FIVE_SECONDS, + ); const [timeRangeOption, setTimeRangeOption] = useState( TimeRangeOptions.FIVE_MINS, ); + const [refresh, setRefresh] = useState(null); const [[from, to], setTimeRange] = useState<[string | null, string | null]>([ null, null, ]); + useEffect(() => { + setRefresh(REFRESH_VALUE[refreshOption]); + }, [refreshOption]); useEffect(() => { const from = TIME_RANGE_TO_FROM_VALUE[timeRangeOption]; setTimeRange([from, "now"]); @@ -126,6 +143,7 @@ export const ServeMetricsSection = ({ const fromParam = from !== null ? `&from=${from}` : ""; const toParam = to !== null ? `&to=${to}` : ""; const timeRangeParams = `${fromParam}${toParam}`; + const refreshParams = refresh ? `&refresh=${refresh}` : ""; return grafanaHost === undefined || !prometheusHealth ? null : ( @@ -143,11 +161,44 @@ export const ServeMetricsSection = ({ className={classes.timeRangeButton} select size="small" - style={{ width: 120 }} + sx={{ width: 80 }} + value={refreshOption} + onChange={({ target: { value } }) => { + setRefreshOption(value as RefreshOptions); + }} + variant="standard" + InputProps={{ + startAdornment: ( + + + + ), + }} + > + {Object.entries(RefreshOptions).map(([key, value]) => ( + + {value} + + ))} + + Auto-refresh interval + { setTimeRangeOption(value as TimeRangeOptions); }} + variant="standard" + InputProps={{ + startAdornment: ( + + + + ), + }} > {Object.entries(TimeRangeOptions).map(([key, value]) => ( @@ -155,12 +206,13 @@ export const ServeMetricsSection = ({ ))} + Time range picker
{metricsConfig.map(({ title, pathParams }) => { const path = `/d-solo/${grafanaServeDashboardUid}?${pathParams}` + - `&refresh${timeRangeParams}&var-datasource=${dashboardDatasource}`; + `${refreshParams}${timeRangeParams}&var-datasource=${dashboardDatasource}`; return (