import React from "react"; import { connect, ConnectedProps } from "react-redux"; import { useHistory } from "react-router-dom"; import queryString from "query-string"; import { makeStyles } from "@material-ui/core/styles"; import Container from "@material-ui/core/Container"; import Grid from "@material-ui/core/Grid"; import Typography from "@material-ui/core/Typography"; import WarningIcon from "@material-ui/icons/Warning"; import InfoIcon from "@material-ui/icons/Info"; import prettyBytes from "pretty-bytes"; import { getMetricsAsync } from "../actions/metricsActions"; import { listQueuesAsync } from "../actions/queuesActions"; import { AppState } from "../store"; import QueueMetricsChart from "../components/QueueMetricsChart"; import Tooltip from "../components/Tooltip"; import { currentUnixtime } from "../utils"; import MetricsFetchControls from "../components/MetricsFetchControls"; import { useQuery } from "../hooks"; import { PrometheusMetricsResponse } from "../api"; const useStyles = makeStyles((theme) => ({ container: { marginTop: 30, paddingTop: theme.spacing(4), paddingBottom: theme.spacing(4), }, controlsContainer: { display: "flex", justifyContent: "flex-end", position: "fixed", background: theme.palette.background.paper, zIndex: theme.zIndex.appBar, right: 0, top: 64, // app-bar height width: "100%", padding: theme.spacing(2), }, chartInfo: { display: "flex", alignItems: "center", marginBottom: theme.spacing(1), }, infoIcon: { marginLeft: theme.spacing(1), color: theme.palette.grey[500], cursor: "pointer", }, errorMessage: { marginLeft: "auto", display: "flex", alignItems: "center", }, warningIcon: { color: "#ff6700", marginRight: 6, }, })); function mapStateToProps(state: AppState) { return { loading: state.metrics.loading, error: state.metrics.error, data: state.metrics.data, pollInterval: state.settings.pollInterval, queues: state.queues.data.map((q) => q.name), }; } const connector = connect(mapStateToProps, { getMetricsAsync, listQueuesAsync, }); type Props = ConnectedProps; const ENDTIME_URL_PARAM_KEY = "end"; const DURATION_URL_PARAM_KEY = "duration"; function MetricsView(props: Props) { const classes = useStyles(); const history = useHistory(); const query = useQuery(); const endTimeStr = query.get(ENDTIME_URL_PARAM_KEY); const endTime = endTimeStr ? parseFloat(endTimeStr) : currentUnixtime(); // default to now const durationStr = query.get(DURATION_URL_PARAM_KEY); const duration = durationStr ? parseFloat(durationStr) : 60 * 60; // default to 1h const { pollInterval, getMetricsAsync, listQueuesAsync, data } = props; const [endTimeSec, setEndTimeSec] = React.useState(endTime); const [durationSec, setDurationSec] = React.useState(duration); const [selectedQueues, setSelectedQueues] = React.useState([]); const handleEndTimeChange = (endTime: number, isEndTimeFixed: boolean) => { const urlQuery = isEndTimeFixed ? { [ENDTIME_URL_PARAM_KEY]: endTime, [DURATION_URL_PARAM_KEY]: durationSec, } : { [DURATION_URL_PARAM_KEY]: durationSec, }; history.push({ ...history.location, search: queryString.stringify(urlQuery), }); setEndTimeSec(endTime); }; const handleDurationChange = (duration: number, isEndTimeFixed: boolean) => { const urlQuery = isEndTimeFixed ? { [ENDTIME_URL_PARAM_KEY]: endTimeSec, [DURATION_URL_PARAM_KEY]: duration, } : { [DURATION_URL_PARAM_KEY]: duration, }; history.push({ ...history.location, search: queryString.stringify(urlQuery), }); setDurationSec(duration); }; const handleAddQueue = (qname: string) => { if (selectedQueues.includes(qname)) { return; } setSelectedQueues(selectedQueues.concat(qname)); }; const handleRemoveQueue = (qname: string) => { if (selectedQueues.length === 1) { return; // ensure that selected queues doesn't go down to zero once user selected } if (selectedQueues.length === 0) { // when user first select filter (remove once of the queues), // we need to lazily initialize the selectedQueues with the rest (all queues but the selected one). setSelectedQueues(props.queues.filter((q) => q !== qname)); return; } setSelectedQueues(selectedQueues.filter((q) => q !== qname)); }; React.useEffect(() => { listQueuesAsync(); }, [listQueuesAsync]); React.useEffect(() => { getMetricsAsync(endTimeSec, durationSec, selectedQueues); }, [pollInterval, getMetricsAsync, durationSec, endTimeSec, selectedQueues]); return (
{data?.tasks_processed_per_second && ( )} {data?.tasks_failed_per_second && ( )} {data?.error_rate && ( )} {data?.queue_size && ( )} {data?.queue_latency_seconds && ( val + "s"} /> )} {data?.queue_size && ( { try { return prettyBytes(val); } catch (error) { return val + "B"; } }} /> )} {data?.pending_tasks_by_queue && ( )} {data?.retry_tasks_by_queue && ( )} {data?.archived_tasks_by_queue && ( )}
); } export default connector(MetricsView); /******** Helper components ********/ interface ChartRowProps { title: string; description: string; metrics: PrometheusMetricsResponse; endTime: number; startTime: number; yAxisTickFormatter?: (val: number) => string; } function ChartRow(props: ChartRowProps) { const classes = useStyles(); return ( <>
{props.title} {props.description}
}> {props.metrics.status === "error" && (
Failed to get metrics data: {props.metrics.error}
)} ); }