UI: More inclusive metadata handling for _count/_sum/_bucket suffixes (#16910)

Although these suffixes always need to be removed before querying metadata for
metrics that follow the Prometheus naming best practices, there can also be
metrics that don't follow these naming practices and have these suffixes
without being part of either a histogram or a summary metric.

Fixes https://github.com/prometheus/prometheus/issues/16907

Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
Julius Volz 2025-07-24 13:54:58 +02:00 committed by GitHub
parent f8b3fce845
commit 13b55ffc81
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 51 additions and 18 deletions

View File

@ -128,13 +128,28 @@ const matchingCriteriaList = (
const SelectorExplainView: FC<SelectorExplainViewProps> = ({ node }) => {
const baseMetricName = node.name.replace(/(_count|_sum|_bucket)$/, "");
const { lookbackDelta } = useSettings();
const { data: metricMeta } = useSuspenseAPIQuery<MetadataResult>({
// Try to get metadata for the full unchanged metric name first.
const { data: fullMetricMeta } = useSuspenseAPIQuery<MetadataResult>({
path: `/metadata`,
params: {
metric: node.name,
},
});
// Also get prefix-stripped metric metadata in case the metadata only exists for
// the histogram / summary base metric name.
const { data: baseMetricMeta } = useSuspenseAPIQuery<MetadataResult>({
path: `/metadata`,
params: {
metric: baseMetricName,
},
});
// Determine which metadata to use.
const metricMeta =
fullMetricMeta.data[node.name] ?? baseMetricMeta.data[baseMetricName];
return (
<Card withBorder>
<Text fz="lg" fw={600} mb="md">
@ -142,17 +157,13 @@ const SelectorExplainView: FC<SelectorExplainViewProps> = ({ node }) => {
selector
</Text>
<Text fz="sm">
{metricMeta.data === undefined ||
metricMeta.data[baseMetricName] === undefined ||
metricMeta.data[baseMetricName].length < 1 ? (
{metricMeta === undefined || metricMeta.length < 1 ? (
<>No metric metadata found.</>
) : (
<>
<strong>Metric help</strong>:{" "}
{metricMeta.data[baseMetricName][0].help}
<strong>Metric help</strong>: {metricMeta[0].help}
<br />
<strong>Metric type</strong>:{" "}
{metricMeta.data[baseMetricName][0].type}
<strong>Metric type</strong>: {metricMeta[0].type}
</>
)}
</Text>
@ -161,7 +172,8 @@ const SelectorExplainView: FC<SelectorExplainViewProps> = ({ node }) => {
{node.type === nodeType.vectorSelector ? (
<>
This node selects the latest (non-stale) sample value within the
last <span className="promql-code promql-duration">{lookbackDelta}</span>
last{" "}
<span className="promql-code promql-duration">{lookbackDelta}</span>
</>
) : (
<>

View File

@ -1,11 +1,23 @@
import { FC, useMemo, useState } from "react";
import { useSuspenseAPIQuery } from "../../../api/api";
import { MetadataResult } from "../../../api/responseTypes/metadata";
import { ActionIcon, CopyButton, Group, Stack, Table, TextInput } from "@mantine/core";
import {
ActionIcon,
CopyButton,
Group,
Stack,
Table,
TextInput,
} from "@mantine/core";
import React from "react";
import { Fuzzy } from "@nexucis/fuzzy";
import sanitizeHTML from "sanitize-html";
import { IconCheck, IconCodePlus, IconCopy, IconZoomCode } from "@tabler/icons-react";
import {
IconCheck,
IconCodePlus,
IconCopy,
IconZoomCode,
} from "@tabler/icons-react";
import LabelsExplorer from "./LabelsExplorer";
import { useDebouncedValue } from "@mantine/hooks";
import classes from "./MetricsExplorer.module.css";
@ -55,10 +67,17 @@ const MetricsExplorer: FC<MetricsExplorerProps> = ({
return getSearchMatches(debouncedFilterText, metricNames);
}, [debouncedFilterText, metricNames]);
const getMeta = (m: string) =>
data.data[m.replace(/(_count|_sum|_bucket)$/, "")] || [
{ help: "unknown", type: "unknown", unit: "unknown" },
];
const getMeta = (m: string) => {
return (
// First check if the full metric name has metadata (even if it has one of the
// histogram/summary suffixes, it may be a metric that is not following naming
// conventions, see https://github.com/prometheus/prometheus/issues/16907).
data.data[m] ??
data.data[m.replace(/(_count|_sum|_bucket)$/, "")] ?? [
{ help: "unknown", type: "unknown", unit: "unknown" },
]
);
};
if (selectedMetric !== null) {
return (

View File

@ -664,9 +664,11 @@ export class HybridComplete implements CompleteStrategy {
.then((metricMetadata) => {
if (metricMetadata) {
for (const [metricName, node] of metricCompletion) {
// For histograms and summaries, the metadata is only exposed for the base metric name,
// not separately for the _count, _sum, and _bucket time series.
const metadata = metricMetadata[metricName.replace(/(_count|_sum|_bucket)$/, '')];
// First check if the full metric name has metadata (even if it has one of the
// histogram/summary suffixes, it may be a metric that is not following naming
// conventions, see https://github.com/prometheus/prometheus/issues/16907).
// Then fall back to the base metric name if full metadata doesn't exist.
const metadata = metricMetadata[metricName] ?? metricMetadata[metricName.replace(/(_count|_sum|_bucket)$/, '')];
if (metadata) {
if (metadata.length > 1) {
// it means the metricName has different possible helper and type