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,
|
pathPrefix,
|
||||||
path,
|
path,
|
||||||
params,
|
params,
|
||||||
|
recordResponseTime,
|
||||||
}: {
|
}: {
|
||||||
pathPrefix: string;
|
pathPrefix: string;
|
||||||
path: string;
|
path: string;
|
||||||
params?: Record<string, string>;
|
params?: Record<string, string>;
|
||||||
|
recordResponseTime?: (time: number) => void;
|
||||||
}) =>
|
}) =>
|
||||||
async ({ signal }: { signal: AbortSignal }) => {
|
async ({ signal }: { signal: AbortSignal }) => {
|
||||||
const queryString = params
|
const queryString = params
|
||||||
|
@ -33,6 +35,8 @@ const createQueryFn =
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`${pathPrefix}/${API_PATH}${path}${queryString}`,
|
`${pathPrefix}/${API_PATH}${path}${queryString}`,
|
||||||
{
|
{
|
||||||
|
@ -54,6 +58,10 @@ const createQueryFn =
|
||||||
|
|
||||||
const apiRes = (await res.json()) as APIResponse<T>;
|
const apiRes = (await res.json()) as APIResponse<T>;
|
||||||
|
|
||||||
|
if (recordResponseTime) {
|
||||||
|
recordResponseTime(Date.now() - startTime);
|
||||||
|
}
|
||||||
|
|
||||||
if (apiRes.status === "error") {
|
if (apiRes.status === "error") {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
apiRes.error !== undefined
|
apiRes.error !== undefined
|
||||||
|
@ -84,6 +92,7 @@ type QueryOptions = {
|
||||||
path: string;
|
path: string;
|
||||||
params?: Record<string, string>;
|
params?: Record<string, string>;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
|
recordResponseTime?: (time: number) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAPIQuery = <T>({
|
export const useAPIQuery = <T>({
|
||||||
|
@ -91,6 +100,7 @@ export const useAPIQuery = <T>({
|
||||||
path,
|
path,
|
||||||
params,
|
params,
|
||||||
enabled,
|
enabled,
|
||||||
|
recordResponseTime,
|
||||||
}: QueryOptions) => {
|
}: QueryOptions) => {
|
||||||
const { pathPrefix } = useSettings();
|
const { pathPrefix } = useSettings();
|
||||||
|
|
||||||
|
@ -100,7 +110,7 @@ export const useAPIQuery = <T>({
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
gcTime: 0,
|
gcTime: 0,
|
||||||
enabled,
|
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 { Histogram } from "../../types/types";
|
||||||
import { bucketRangeString } from "./HistogramHelpers";
|
import { bucketRangeString } from "./HistogramHelpers";
|
||||||
import { useSettings } from "../../state/settingsSlice";
|
import { useSettings } from "../../state/settingsSlice";
|
||||||
|
import { useAppDispatch, useAppSelector } from "../../state/hooks";
|
||||||
|
import { setVisualizer } from "../../state/queryPageSlice";
|
||||||
|
import TimeInput from "./TimeInput";
|
||||||
dayjs.extend(timezone);
|
dayjs.extend(timezone);
|
||||||
|
|
||||||
const maxFormattableSeries = 1000;
|
const maxFormattableSeries = 1000;
|
||||||
|
@ -51,14 +54,21 @@ const limitSeries = <S extends InstantSample | RangeSamples>(
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface DataTableProps {
|
export interface DataTableProps {
|
||||||
expr: string;
|
panelIdx: number;
|
||||||
evalTime: number | null;
|
|
||||||
retriggerIdx: number;
|
retriggerIdx: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
|
const DataTable: FC<DataTableProps> = ({ panelIdx, retriggerIdx }) => {
|
||||||
const [scale, setScale] = useState<string>("exponential");
|
const [scale, setScale] = useState<string>("exponential");
|
||||||
const [limitResults, setLimitResults] = useState<boolean>(true);
|
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 } =
|
const { data, error, isFetching, isLoading, refetch } =
|
||||||
useAPIQuery<InstantQueryResult>({
|
useAPIQuery<InstantQueryResult>({
|
||||||
|
@ -66,14 +76,15 @@ const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
|
||||||
path: "/query",
|
path: "/query",
|
||||||
params: {
|
params: {
|
||||||
query: expr,
|
query: expr,
|
||||||
time: `${(evalTime !== null ? evalTime : Date.now()) / 1000}`,
|
time: `${(endTime !== null ? endTime : Date.now()) / 1000}`,
|
||||||
},
|
},
|
||||||
enabled: expr !== "",
|
enabled: expr !== "",
|
||||||
|
recordResponseTime: setResponseTime,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
expr !== "" && refetch();
|
expr !== "" && refetch();
|
||||||
}, [retriggerIdx, refetch, expr, evalTime]);
|
}, [retriggerIdx, refetch, expr, endTime]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
setLimitResults(true);
|
setLimitResults(true);
|
||||||
|
@ -121,7 +132,40 @@ const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
|
||||||
const doFormat = result.length <= maxFormattableSeries;
|
const doFormat = result.length <= maxFormattableSeries;
|
||||||
|
|
||||||
return (
|
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 &&
|
{limitResults &&
|
||||||
["vector", "matrix"].includes(resultType) &&
|
["vector", "matrix"].includes(resultType) &&
|
||||||
result.length > maxDisplayableSeries && (
|
result.length > maxDisplayableSeries && (
|
||||||
|
@ -137,6 +181,7 @@ const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
|
||||||
</Anchor>
|
</Anchor>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!doFormat && (
|
{!doFormat && (
|
||||||
<Alert
|
<Alert
|
||||||
title="Formatting turned off"
|
title="Formatting turned off"
|
||||||
|
@ -146,6 +191,7 @@ const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
|
||||||
formatting for performance reasons.
|
formatting for performance reasons.
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Box pos="relative" className={classes.tableWrapper}>
|
<Box pos="relative" className={classes.tableWrapper}>
|
||||||
<LoadingOverlay
|
<LoadingOverlay
|
||||||
visible={isFetching}
|
visible={isFetching}
|
||||||
|
@ -156,10 +202,12 @@ const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
|
||||||
}}
|
}}
|
||||||
styles={{ loader: { width: "100%", height: "100%" } }}
|
styles={{ loader: { width: "100%", height: "100%" } }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Table fz="xs">
|
<Table fz="xs">
|
||||||
<Table.Tbody>
|
<Table.Tbody>
|
||||||
{resultType === "vector" ? (
|
{resultType === "vector" ? (
|
||||||
limitSeries<InstantSample>(result, limitResults).map((s, idx) => (
|
limitSeries<InstantSample>(result, limitResults).map(
|
||||||
|
(s, idx) => (
|
||||||
<Table.Tr key={idx}>
|
<Table.Tr key={idx}>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<SeriesName labels={s.metric} format={doFormat} />
|
<SeriesName labels={s.metric} format={doFormat} />
|
||||||
|
@ -173,10 +221,15 @@ const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
|
||||||
index={idx}
|
index={idx}
|
||||||
scale={scale}
|
scale={scale}
|
||||||
/>
|
/>
|
||||||
<Group justify="space-between" align="center" p={10}>
|
<Group
|
||||||
|
justify="space-between"
|
||||||
|
align="center"
|
||||||
|
p={10}
|
||||||
|
>
|
||||||
<Group align="center" gap="1rem">
|
<Group align="center" gap="1rem">
|
||||||
<span>
|
<span>
|
||||||
<strong>Count:</strong> {s.histogram[1].count}
|
<strong>Count:</strong>{" "}
|
||||||
|
{s.histogram[1].count}
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<strong>Sum:</strong> {s.histogram[1].sum}
|
<strong>Sum:</strong> {s.histogram[1].sum}
|
||||||
|
@ -197,9 +250,11 @@ const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
|
||||||
)}
|
)}
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
))
|
)
|
||||||
|
)
|
||||||
) : resultType === "matrix" ? (
|
) : resultType === "matrix" ? (
|
||||||
limitSeries<RangeSamples>(result, limitResults).map((s, idx) => (
|
limitSeries<RangeSamples>(result, limitResults).map(
|
||||||
|
(s, idx) => (
|
||||||
<Table.Tr key={idx}>
|
<Table.Tr key={idx}>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<SeriesName labels={s.metric} format={doFormat} />
|
<SeriesName labels={s.metric} format={doFormat} />
|
||||||
|
@ -221,11 +276,14 @@ const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
|
||||||
))}
|
))}
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
))
|
)
|
||||||
|
)
|
||||||
) : resultType === "scalar" ? (
|
) : resultType === "scalar" ? (
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
<Table.Td>Scalar value</Table.Td>
|
<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>
|
</Table.Tr>
|
||||||
) : resultType === "string" ? (
|
) : resultType === "string" ? (
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
|
@ -245,6 +303,8 @@ const DataTable: FC<DataTableProps> = ({ expr, evalTime, retriggerIdx }) => {
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -84,26 +84,7 @@ const QueryPanel: FC<PanelProps> = ({ idx, metricNames }) => {
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
<Tabs.Panel pt="sm" value="table">
|
<Tabs.Panel pt="sm" value="table">
|
||||||
<Stack gap="lg" mt="sm">
|
<DataTable panelIdx={idx} retriggerIdx={retriggerIdx} />
|
||||||
<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>
|
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
<Tabs.Panel
|
<Tabs.Panel
|
||||||
pt="sm"
|
pt="sm"
|
||||||
|
|
|
@ -1,12 +1,4 @@
|
||||||
import {
|
import { ActionIcon, Box, Group, Input, Select, Skeleton } from "@mantine/core";
|
||||||
ActionIcon,
|
|
||||||
Box,
|
|
||||||
Checkbox,
|
|
||||||
Group,
|
|
||||||
Input,
|
|
||||||
Select,
|
|
||||||
Skeleton,
|
|
||||||
} from "@mantine/core";
|
|
||||||
import {
|
import {
|
||||||
IconLayoutNavbarCollapse,
|
IconLayoutNavbarCollapse,
|
||||||
IconLayoutNavbarExpand,
|
IconLayoutNavbarExpand,
|
||||||
|
|
|
@ -36,7 +36,7 @@ export const getEffectiveResolution = (
|
||||||
: resolution.density === "medium"
|
: resolution.density === "medium"
|
||||||
? 250
|
? 250
|
||||||
: 100;
|
: 100;
|
||||||
return Math.max(Math.floor(range / factor), 1);
|
return Math.max(Math.floor(range / factor / 1000) * 1000, 1000);
|
||||||
}
|
}
|
||||||
case "fixed":
|
case "fixed":
|
||||||
return resolution.value;
|
return resolution.value;
|
||||||
|
|
Loading…
Reference in a new issue