diff --git a/web/ui/mantine-ui/src/pages/query/TreeNode.tsx b/web/ui/mantine-ui/src/pages/query/TreeNode.tsx index 5e73d040c0..5be9efd849 100644 --- a/web/ui/mantine-ui/src/pages/query/TreeNode.tsx +++ b/web/ui/mantine-ui/src/pages/query/TreeNode.tsx @@ -7,9 +7,18 @@ import { useState, } from "react"; import ASTNode, { nodeType } from "../../promql/ast"; -import { getNodeChildren } from "../../promql/utils"; +import { escapeString, getNodeChildren } from "../../promql/utils"; import { formatNode } from "../../promql/format"; -import { Box, CSSProperties, Group, Loader, Text } from "@mantine/core"; +import { + Box, + Code, + CSSProperties, + Group, + List, + Loader, + Text, + Tooltip, +} from "@mantine/core"; import { useAPIQuery } from "../../api/api"; import { InstantQueryResult, @@ -21,8 +30,10 @@ import { IconPointFilled } from "@tabler/icons-react"; import classes from "./TreeNode.module.css"; import clsx from "clsx"; import { useId } from "@mantine/hooks"; +import { functionSignatures } from "../../promql/functionSignatures"; const nodeIndent = 20; +const maxLabels = 10; type NodeState = "waiting" | "running" | "error" | "success"; @@ -68,12 +79,12 @@ const TreeNode: FC<{ const [responseTime, setResponseTime] = useState(0); const [resultStats, setResultStats] = useState<{ numSeries: number; - labelCardinalities: Record; labelExamples: Record; + sortedLabelCards: [string, number][]; }>({ numSeries: 0, - labelCardinalities: {}, labelExamples: {}, + sortedLabelCards: [], }); const children = getNodeChildren(node); @@ -86,11 +97,24 @@ const TreeNode: FC<{ [childStates] ); + // Optimize range vector selector fetches to give us the info we're looking for + // more cheaply. E.g. 'foo[7w]' can be expensive to fully fetch, but wrapping it + // in 'last_over_time(foo[7w])' is cheaper and also gives us all the info we + // need (number of series and labels). + let queryNode = node; + if (queryNode.type === nodeType.matrixSelector) { + queryNode = { + type: nodeType.call, + func: functionSignatures["last_over_time"], + args: [node], + }; + } + const { data, error, isFetching } = useAPIQuery({ key: [useId()], path: "/query", params: { - query: serializeNode(node), + query: serializeNode(queryNode), }, recordResponseTime: setResponseTime, enabled: mergedChildState === "success", @@ -145,7 +169,7 @@ const TreeNode: FC<{ borderTopLeftRadius: undefined, })); } - }, [parentRef, reverse, nodeRef]); + }, [parentRef, reverse, nodeRef, setConnectorStyle]); // Update the node info state based on the query result. useEffect(() => { @@ -166,17 +190,12 @@ const TreeNode: FC<{ result.forEach((s: InstantSample | RangeSamples) => { Object.entries(s.metric).forEach(([ln, lv]) => { // TODO: If we ever want to include __name__ here again, we cannot use the - // count_over_time(foo[7d]) optimization since that removes the metric name. + // last_over_time(foo[7d]) optimization since that removes the metric name. if (ln !== "__name__") { - if (!labelValuesByName.hasOwnProperty(ln)) { - labelValuesByName[ln] = { [lv]: 1 }; - } else { - if (!labelValuesByName[ln].hasOwnProperty(lv)) { - labelValuesByName[ln][lv] = 1; - } else { - labelValuesByName[ln][lv]++; - } + if (!labelValuesByName[ln]) { + labelValuesByName[ln] = {}; } + labelValuesByName[ln][lv] = (labelValuesByName[ln][lv] || 0) + 1; } }); }); @@ -196,7 +215,9 @@ const TreeNode: FC<{ setResultStats({ numSeries: resultSeries, - labelCardinalities, + sortedLabelCards: Object.entries(labelCardinalities).sort( + (a, b) => b[1] - a[1] + ), labelExamples, }); }, [data]); @@ -204,7 +225,7 @@ const TreeNode: FC<{ const innerNode = ( ) : ( - - {resultStats.numSeries} result{resultStats.numSeries !== 1 && "s"} –{" "} - {responseTime}ms - {/* {resultStats.numSeries > 0 && ( - <> - {labelNames.length > 0 ? ( - labelNames.map((v, idx) => ( - - {idx !== 0 && ", "} - {v} - - )) - ) : ( - <>no labels - )} - - )} */} - + + + {resultStats.numSeries} result{resultStats.numSeries !== 1 && "s"} +   –   + {responseTime}ms   –   + + + {resultStats.sortedLabelCards + .slice(0, maxLabels) + .map(([ln, cnt]) => ( + + + {resultStats.labelExamples[ln].map( + ({ value, count }) => ( + + + {escapeString(value)} + {" "} + ({count} + x) + + ) + )} + {cnt > 5 &&
  • ...
  • } +
    + + } + > + + + {ln} + + + : {cnt} + + +
    + ))} + {resultStats.sortedLabelCards.length > maxLabels ? ( + + ...{resultStats.sortedLabelCards.length - maxLabels} more... + + ) : null} +
    +
    )} );