mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -08:00
Refactor DataTable, record & show query stats
Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
parent
5dca5a4718
commit
a6b085ee5a
|
@ -22,10 +22,12 @@ const createQueryFn =
|
|||
pathPrefix,
|
||||
path,
|
||||
params,
|
||||
recordResponseTime,
|
||||
}: {
|
||||
pathPrefix: string;
|
||||
path: string;
|
||||
params?: Record<string, string>;
|
||||
recordResponseTime?: (time: number) => void;
|
||||
}) =>
|
||||
async ({ signal }: { signal: AbortSignal }) => {
|
||||
const queryString = params
|
||||
|
@ -33,6 +35,8 @@ const createQueryFn =
|
|||
: "";
|
||||
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
|
||||
const res = await fetch(
|
||||
`${pathPrefix}/${API_PATH}${path}${queryString}`,
|
||||
{
|
||||
|
@ -54,6 +58,10 @@ const createQueryFn =
|
|||
|
||||
const apiRes = (await res.json()) as APIResponse<T>;
|
||||
|
||||
if (recordResponseTime) {
|
||||
recordResponseTime(Date.now() - startTime);
|
||||
}
|
||||
|
||||
if (apiRes.status === "error") {
|
||||
throw new Error(
|
||||
apiRes.error !== undefined
|
||||
|
@ -84,6 +92,7 @@ type QueryOptions = {
|
|||
path: string;
|
||||
params?: Record<string, string>;
|
||||
enabled?: boolean;
|
||||
recordResponseTime?: (time: number) => void;
|
||||
};
|
||||
|
||||
export const useAPIQuery = <T>({
|
||||
|
@ -91,6 +100,7 @@ export const useAPIQuery = <T>({
|
|||
path,
|
||||
params,
|
||||
enabled,
|
||||
recordResponseTime,
|
||||
}: QueryOptions) => {
|
||||
const { pathPrefix } = useSettings();
|
||||
|
||||
|
@ -100,7 +110,7 @@ export const useAPIQuery = <T>({
|
|||
refetchOnWindowFocus: false,
|
||||
gcTime: 0,
|
||||
enabled,
|
||||
queryFn: createQueryFn({ pathPrefix, path, params }),
|
||||
queryFn: createQueryFn({ pathPrefix, path, params, recordResponseTime }),
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -35,6 +35,9 @@ import HistogramChart from "./HistogramChart";
|
|||
import { Histogram } from "../../types/types";
|
||||
import { bucketRangeString } from "./HistogramHelpers";
|
||||
import { useSettings } from "../../state/settingsSlice";
|
||||
import { useAppDispatch, useAppSelector } from "../../state/hooks";
|
||||
import { setVisualizer } from "../../state/queryPageSlice";
|
||||
import TimeInput from "./TimeInput";
|
||||
dayjs.extend(timezone);
|
||||
|
||||
const maxFormattableSeries = 1000;
|
||||
|
@ -51,14 +54,21 @@ const limitSeries = <S extends InstantSample | RangeSamples>(
|
|||
};
|
||||
|
||||
export interface DataTableProps {
|
||||
expr: string;
|
||||
evalTime: number | null;
|
||||
panelIdx: number;
|
||||
retriggerIdx: number;
|
||||
}
|
||||
|
||||
const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
|
||||
const DataTable: FC<DataTableProps> = ({ panelIdx, retriggerIdx }) => {
|
||||
const [scale, setScale] = useState<string>("exponential");
|
||||
const [limitResults, setLimitResults] = useState<boolean>(true);
|
||||
const [responseTime, setResponseTime] = useState<number>(0);
|
||||
|
||||
const { expr, visualizer } = useAppSelector(
|
||||
(state) => state.queryPage.panels[panelIdx]
|
||||
);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { endTime, range } = visualizer;
|
||||
|
||||
const { data, error, isFetching, isLoading, refetch } =
|
||||
useAPIQuery<InstantQueryResult>({
|
||||
|
@ -66,14 +76,15 @@ const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
|
|||
path: "/query",
|
||||
params: {
|
||||
query: expr,
|
||||
time: `${(evalTime !== null ? evalTime : Date.now()) / 1000}`,
|
||||
time: `${(endTime !== null ? endTime : Date.now()) / 1000}`,
|
||||
},
|
||||
enabled: expr !== "",
|
||||
recordResponseTime: setResponseTime,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
expr !== "" && refetch();
|
||||
}, [retriggerIdx, refetch, expr, evalTime]);
|
||||
}, [retriggerIdx, refetch, expr, endTime]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setLimitResults(true);
|
||||
|
@ -121,7 +132,40 @@ const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
|
|||
const doFormat = result.length <= maxFormattableSeries;
|
||||
|
||||
return (
|
||||
<Stack gap="lg" mt="sm">
|
||||
{isLoading ? (
|
||||
<Box>
|
||||
{Array.from(Array(5), (_, i) => (
|
||||
<Skeleton key={i} height={30} mb={15} />
|
||||
))}
|
||||
</Box>
|
||||
) : data === undefined ? (
|
||||
<Alert variant="transparent">No data queried yet</Alert>
|
||||
) : result.length === 0 ? (
|
||||
<Alert title="Empty query result" icon={<IconInfoCircle size={14} />}>
|
||||
This query returned no data.
|
||||
</Alert>
|
||||
) : (
|
||||
<>
|
||||
<Group justify="space-between">
|
||||
<TimeInput
|
||||
time={endTime}
|
||||
range={range}
|
||||
description="Evaluation time"
|
||||
onChangeTime={(time) =>
|
||||
dispatch(
|
||||
setVisualizer({
|
||||
idx: panelIdx,
|
||||
visualizer: { ...visualizer, endTime: time },
|
||||
})
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Text size="xs" c="gray">
|
||||
Load time: {responseTime}ms   Result series: {result.length}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
{limitResults &&
|
||||
["vector", "matrix"].includes(resultType) &&
|
||||
result.length > maxDisplayableSeries && (
|
||||
|
@ -137,6 +181,7 @@ const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
|
|||
</Anchor>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{!doFormat && (
|
||||
<Alert
|
||||
title="Formatting turned off"
|
||||
|
@ -146,6 +191,7 @@ const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
|
|||
formatting for performance reasons.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Box pos="relative" className={classes.tableWrapper}>
|
||||
<LoadingOverlay
|
||||
visible={isFetching}
|
||||
|
@ -156,10 +202,12 @@ const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
|
|||
}}
|
||||
styles={{ loader: { width: "100%", height: "100%" } }}
|
||||
/>
|
||||
|
||||
<Table fz="xs">
|
||||
<Table.Tbody>
|
||||
{resultType === "vector" ? (
|
||||
limitSeries<InstantSample>(result, limitResults).map((s, idx) => (
|
||||
limitSeries<InstantSample>(result, limitResults).map(
|
||||
(s, idx) => (
|
||||
<Table.Tr key={idx}>
|
||||
<Table.Td>
|
||||
<SeriesName labels={s.metric} format={doFormat} />
|
||||
|
@ -173,10 +221,15 @@ const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
|
|||
index={idx}
|
||||
scale={scale}
|
||||
/>
|
||||
<Group justify="space-between" align="center" p={10}>
|
||||
<Group
|
||||
justify="space-between"
|
||||
align="center"
|
||||
p={10}
|
||||
>
|
||||
<Group align="center" gap="1rem">
|
||||
<span>
|
||||
<strong>Count:</strong> {s.histogram[1].count}
|
||||
<strong>Count:</strong>{" "}
|
||||
{s.histogram[1].count}
|
||||
</span>
|
||||
<span>
|
||||
<strong>Sum:</strong> {s.histogram[1].sum}
|
||||
|
@ -197,9 +250,11 @@ const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
|
|||
)}
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
))
|
||||
)
|
||||
)
|
||||
) : resultType === "matrix" ? (
|
||||
limitSeries<RangeSamples>(result, limitResults).map((s, idx) => (
|
||||
limitSeries<RangeSamples>(result, limitResults).map(
|
||||
(s, idx) => (
|
||||
<Table.Tr key={idx}>
|
||||
<Table.Td>
|
||||
<SeriesName labels={s.metric} format={doFormat} />
|
||||
|
@ -221,11 +276,14 @@ const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
|
|||
))}
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
))
|
||||
)
|
||||
)
|
||||
) : resultType === "scalar" ? (
|
||||
<Table.Tr>
|
||||
<Table.Td>Scalar value</Table.Td>
|
||||
<Table.Td className={classes.numberCell}>{result[1]}</Table.Td>
|
||||
<Table.Td className={classes.numberCell}>
|
||||
{result[1]}
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
) : resultType === "string" ? (
|
||||
<Table.Tr>
|
||||
|
@ -245,6 +303,8 @@ const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
|
|||
</Table>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -84,26 +84,7 @@ const QueryPanel: FC<PanelProps> = ({ idx, metricNames }) => {
|
|||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel pt="sm" value="table">
|
||||
<Stack gap="lg" mt="sm">
|
||||
<TimeInput
|
||||
time={panel.visualizer.endTime}
|
||||
range={panel.visualizer.range}
|
||||
description="Evaluation time"
|
||||
onChangeTime={(time) =>
|
||||
dispatch(
|
||||
setVisualizer({
|
||||
idx,
|
||||
visualizer: { ...panel.visualizer, endTime: time },
|
||||
})
|
||||
)
|
||||
}
|
||||
/>
|
||||
<DataTable
|
||||
expr={panel.expr}
|
||||
evalTime={panel.visualizer.endTime}
|
||||
retriggerIdx={retriggerIdx}
|
||||
/>
|
||||
</Stack>
|
||||
<DataTable panelIdx={idx} retriggerIdx={retriggerIdx} />
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel
|
||||
pt="sm"
|
||||
|
|
|
@ -1,12 +1,4 @@
|
|||
import {
|
||||
ActionIcon,
|
||||
Box,
|
||||
Checkbox,
|
||||
Group,
|
||||
Input,
|
||||
Select,
|
||||
Skeleton,
|
||||
} from "@mantine/core";
|
||||
import { ActionIcon, Box, Group, Input, Select, Skeleton } from "@mantine/core";
|
||||
import {
|
||||
IconLayoutNavbarCollapse,
|
||||
IconLayoutNavbarExpand,
|
||||
|
|
|
@ -36,7 +36,7 @@ export const getEffectiveResolution = (
|
|||
: resolution.density === "medium"
|
||||
? 250
|
||||
: 100;
|
||||
return Math.max(Math.floor(range / factor), 1);
|
||||
return Math.max(Math.floor(range / factor / 1000) * 1000, 1000);
|
||||
}
|
||||
case "fixed":
|
||||
return resolution.value;
|
||||
|
|
Loading…
Reference in a new issue