From 4eeeb6ee88e69d79884127775598467f922c7e2a Mon Sep 17 00:00:00 2001 From: Julius Volz Date: Thu, 12 Jun 2025 16:16:41 +0200 Subject: [PATCH] Fetch and display full query stats in hover tooltip in table query tab (#16723) Fixes https://github.com/prometheus/prometheus/issues/5857 Signed-off-by: Julius Volz --- .../mantine-ui/src/api/responseTypes/query.ts | 11 ++++- .../src/pages/query/QueryStatsDisplay.tsx | 49 +++++++++++++++++++ .../mantine-ui/src/pages/query/TableTab.tsx | 13 +++-- 3 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 web/ui/mantine-ui/src/pages/query/QueryStatsDisplay.tsx diff --git a/web/ui/mantine-ui/src/api/responseTypes/query.ts b/web/ui/mantine-ui/src/api/responseTypes/query.ts index 327b049b5f..00dcb92cc5 100644 --- a/web/ui/mantine-ui/src/api/responseTypes/query.ts +++ b/web/ui/mantine-ui/src/api/responseTypes/query.ts @@ -23,9 +23,14 @@ export interface RangeSamples { export type SampleValue = [number, string]; export type SampleHistogram = [number, Histogram]; +export type QueryStats = { + timings: Record; + samples: Record; +}; + // Result type for /api/v1/query endpoint. // See: https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries -export type InstantQueryResult = +export type InstantQueryResult = ( | { resultType: "vector"; result: InstantSample[]; @@ -41,11 +46,13 @@ export type InstantQueryResult = | { resultType: "string"; result: SampleValue; - }; + } +) & { stats?: QueryStats }; // Result type for /api/v1/query_range endpoint. // See: https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries export type RangeQueryResult = { resultType: "matrix"; result: RangeSamples[]; + stats?: QueryStats; }; diff --git a/web/ui/mantine-ui/src/pages/query/QueryStatsDisplay.tsx b/web/ui/mantine-ui/src/pages/query/QueryStatsDisplay.tsx new file mode 100644 index 0000000000..807bec6b22 --- /dev/null +++ b/web/ui/mantine-ui/src/pages/query/QueryStatsDisplay.tsx @@ -0,0 +1,49 @@ +import { FC } from "react"; +import { Box, Text, Tooltip, Table } from "@mantine/core"; +import { QueryStats } from "../../api/responseTypes/query"; + +const statsTable = (stats: Record) => { + return ( + + + {Object.entries(stats).map(([k, v]) => ( + + + {k} + + + {v} + + + ))} + +
+ ); +}; + +const QueryStatsDisplay: FC<{ + numResults: number; + responseTime: number; + stats: QueryStats; +}> = ({ numResults, responseTime, stats }) => { + return ( + + Timing stats (s): + {statsTable(stats.timings)} + + Sample stats: + + {statsTable(stats.samples)} + + } + > + + Load time: {responseTime}ms   Result series: {numResults} + + + ); +}; + +export default QueryStatsDisplay; diff --git a/web/ui/mantine-ui/src/pages/query/TableTab.tsx b/web/ui/mantine-ui/src/pages/query/TableTab.tsx index 5e2f804243..e0d2ed7dff 100644 --- a/web/ui/mantine-ui/src/pages/query/TableTab.tsx +++ b/web/ui/mantine-ui/src/pages/query/TableTab.tsx @@ -1,5 +1,5 @@ import { FC, useEffect, useId, useLayoutEffect, useState } from "react"; -import { Alert, Skeleton, Box, Group, Stack, Text } from "@mantine/core"; +import { Alert, Skeleton, Box, Group, Stack } from "@mantine/core"; import { IconAlertTriangle, IconInfoCircle } from "@tabler/icons-react"; import { InstantQueryResult } from "../../api/responseTypes/query"; import { useAPIQuery } from "../../api/api"; @@ -9,6 +9,7 @@ import { useAppDispatch, useAppSelector } from "../../state/hooks"; import { setVisualizer } from "../../state/queryPageSlice"; import TimeInput from "./TimeInput"; import DataTable from "./DataTable"; +import QueryStatsDisplay from "./QueryStatsDisplay"; dayjs.extend(timezone); export interface TableTabProps { @@ -34,6 +35,7 @@ const TableTab: FC = ({ panelIdx, retriggerIdx, expr }) => { params: { query: expr, time: `${(endTime !== null ? endTime : Date.now()) / 1000}`, + stats: "true", }, enabled: expr !== "", recordResponseTime: setResponseTime, @@ -66,10 +68,11 @@ const TableTab: FC = ({ panelIdx, retriggerIdx, expr }) => { } /> {!isFetching && data !== undefined && ( - - Load time: {responseTime}ms   Result series:{" "} - {data.data.result.length} - + )} {isFetching ? (