mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2025-01-20 15:16:42 +00:00
vmui: support for Prometheus data on the cardinality page (#4713)
* feat: add cardinality support for prometheus (#4320) * docs/CHANGELOG.md: add cardinality support for prometheus --------- Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
This commit is contained in:
parent
d8258be292
commit
9bd71b0f12
9 changed files with 124 additions and 48 deletions
|
@ -12,7 +12,7 @@ import CardinalityTotals, { CardinalityTotalsProps } from "../CardinalityTotals/
|
|||
import useSearchParamsFromObject from "../../../hooks/useSearchParamsFromObject";
|
||||
import useStateSearchParams from "../../../hooks/useStateSearchParams";
|
||||
|
||||
const CardinalityConfigurator: FC<CardinalityTotalsProps> = (props) => {
|
||||
const CardinalityConfigurator: FC<CardinalityTotalsProps> = ({ isPrometheus, ...props }) => {
|
||||
const { isMobile } = useDeviceDetect();
|
||||
const [searchParams] = useSearchParams();
|
||||
const { setSearchParamsFromKeys } = useSearchParamsFromObject();
|
||||
|
@ -93,15 +93,20 @@ const CardinalityConfigurator: FC<CardinalityTotalsProps> = (props) => {
|
|||
<TextField
|
||||
label="Limit entries"
|
||||
type="number"
|
||||
value={topN}
|
||||
value={isPrometheus ? 10 : topN}
|
||||
error={errorTopN}
|
||||
disabled={isPrometheus}
|
||||
helperText={isPrometheus ? "not available for Prometheus" : ""}
|
||||
onChange={handleTopNChange}
|
||||
onEnter={handleRunQuery}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="vm-cardinality-configurator-bottom">
|
||||
<CardinalityTotals {...props}/>
|
||||
<CardinalityTotals
|
||||
isPrometheus={isPrometheus}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
<div className="vm-cardinality-configurator-bottom-helpful">
|
||||
<a
|
||||
|
|
|
@ -13,13 +13,15 @@ export interface CardinalityTotalsProps {
|
|||
totalSeriesPrev: number;
|
||||
totalLabelValuePairs: number;
|
||||
seriesCountByMetricName: TopHeapEntry[];
|
||||
isPrometheus?: boolean;
|
||||
}
|
||||
|
||||
const CardinalityTotals: FC<CardinalityTotalsProps> = ({
|
||||
totalSeries,
|
||||
totalSeriesPrev,
|
||||
totalSeriesAll,
|
||||
seriesCountByMetricName
|
||||
totalSeries = 0,
|
||||
totalSeriesPrev = 0,
|
||||
totalSeriesAll = 0,
|
||||
seriesCountByMetricName = [],
|
||||
isPrometheus
|
||||
}) => {
|
||||
const { isMobile } = useDeviceDetect();
|
||||
|
||||
|
@ -36,7 +38,7 @@ const CardinalityTotals: FC<CardinalityTotalsProps> = ({
|
|||
{
|
||||
title: "Total series",
|
||||
value: totalSeries.toLocaleString("en-US"),
|
||||
dynamic: !totalSeries || !totalSeriesPrev ? "" : `${dynamic.toFixed(2)}%`,
|
||||
dynamic: (!totalSeries || !totalSeriesPrev || isPrometheus) ? "" : `${dynamic.toFixed(2)}%`,
|
||||
display: !focusLabel,
|
||||
info: `The total number of active time series.
|
||||
A time series is uniquely identified by its name plus a set of its labels.
|
||||
|
|
|
@ -22,6 +22,7 @@ interface MetricsProperties {
|
|||
sectionTitle: string;
|
||||
tip?: string;
|
||||
tableHeaderCells: HeadCell[];
|
||||
isPrometheus: boolean;
|
||||
}
|
||||
|
||||
const MetricsContent: FC<MetricsProperties> = ({
|
||||
|
@ -34,10 +35,13 @@ const MetricsContent: FC<MetricsProperties> = ({
|
|||
sectionTitle,
|
||||
tip,
|
||||
tableHeaderCells,
|
||||
isPrometheus,
|
||||
}) => {
|
||||
const { isMobile } = useDeviceDetect();
|
||||
const [activeTab, setActiveTab] = useState("table");
|
||||
|
||||
const noDataPrometheus = isPrometheus && !rows.length;
|
||||
|
||||
const tableCells = (row: Data) => (
|
||||
<TableCells
|
||||
row={row}
|
||||
|
@ -90,8 +94,19 @@ const MetricsContent: FC<MetricsProperties> = ({
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{activeTab === "table" && (
|
||||
{noDataPrometheus && (
|
||||
<div className="vm-metrics-content-prom-data">
|
||||
<div className="vm-metrics-content-prom-data__icon"><InfoIcon/></div>
|
||||
<h3 className="vm-metrics-content-prom-data__title">
|
||||
Prometheus Data Limitation
|
||||
</h3>
|
||||
<p className="vm-metrics-content-prom-data__text">
|
||||
Due to missing data from your Prometheus source, some tables may appear empty.<br/>
|
||||
This does not indicate an issue with your system or our tool.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{!noDataPrometheus && activeTab === "table" && (
|
||||
<div
|
||||
ref={chartContainer}
|
||||
className={classNames({
|
||||
|
@ -107,7 +122,7 @@ const MetricsContent: FC<MetricsProperties> = ({
|
|||
/>
|
||||
</div>
|
||||
)}
|
||||
{activeTab === "graph" && (
|
||||
{!noDataPrometheus && activeTab === "graph" && (
|
||||
<div className="vm-metrics-content__chart">
|
||||
<SimpleBarChart data={rows.map(({ name, value }) => ({ name, value }))}/>
|
||||
</div>
|
||||
|
|
|
@ -59,4 +59,35 @@
|
|||
&__chart {
|
||||
padding-top: $padding-medium;
|
||||
}
|
||||
|
||||
&-prom-data {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
gap: $padding-small;
|
||||
margin-top: $padding-global;
|
||||
text-align: center;
|
||||
|
||||
&__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-bottom: $padding-small;
|
||||
color: $color-primary;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: $font-size-medium;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&__text {
|
||||
max-width: 700px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,14 +10,17 @@ interface AppState {
|
|||
export default class AppConfigurator {
|
||||
private tsdbStatus: TSDBStatus;
|
||||
private tabsNames: string[];
|
||||
private isPrometheus: boolean;
|
||||
|
||||
constructor() {
|
||||
this.tsdbStatus = this.defaultTSDBStatus;
|
||||
this.tabsNames = ["table", "graph"];
|
||||
this.isPrometheus = false;
|
||||
this.getDefaultState = this.getDefaultState.bind(this);
|
||||
}
|
||||
|
||||
set tsdbStatusData(tsdbStatus: TSDBStatus) {
|
||||
this.isPrometheus = !!tsdbStatus?.headStats;
|
||||
this.tsdbStatus = tsdbStatus;
|
||||
}
|
||||
|
||||
|
@ -39,6 +42,10 @@ export default class AppConfigurator {
|
|||
};
|
||||
}
|
||||
|
||||
get isPrometheusData(): boolean {
|
||||
return this.isPrometheus;
|
||||
}
|
||||
|
||||
keys(match?: string | null, focusLabel?: string | null): string[] {
|
||||
const isMetric = match && /__name__=".+"/.test(match);
|
||||
const isLabel = match && /{.+=".+"}/g.test(match);
|
||||
|
|
|
@ -27,6 +27,30 @@ export const useFetchQuery = (): {
|
|||
const [error, setError] = useState<ErrorTypes | string>();
|
||||
const [tsdbStatus, setTSDBStatus] = useState<TSDBStatus>(appConfigurator.defaultTSDBStatus);
|
||||
|
||||
const getResponseJson = async (url: string) => {
|
||||
const response = await fetch(url);
|
||||
if (response.ok) {
|
||||
return await response.json();
|
||||
}
|
||||
throw new Error(`Request failed with status ${response.status}`);
|
||||
};
|
||||
|
||||
const calculateDiffs = (result: TSDBStatus, prevResult: TSDBStatus) => {
|
||||
Object.keys(result).forEach(k => {
|
||||
const key = k as keyof TSDBStatus;
|
||||
const entries = result[key];
|
||||
const prevEntries = prevResult[key];
|
||||
|
||||
if (Array.isArray(entries) && Array.isArray(prevEntries)) {
|
||||
entries.forEach((entry) => {
|
||||
const valuePrev = prevEntries.find(prevEntry => prevEntry.name === entry.name)?.value;
|
||||
entry.diff = valuePrev ? entry.value - valuePrev : 0;
|
||||
entry.valuePrev = valuePrev || 0;
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const fetchCardinalityInfo = async (requestParams: CardinalityRequestsParams) => {
|
||||
if (!serverUrl) return;
|
||||
setError("");
|
||||
|
@ -34,64 +58,52 @@ export const useFetchQuery = (): {
|
|||
setTSDBStatus(appConfigurator.defaultTSDBStatus);
|
||||
|
||||
const totalParams = {
|
||||
...requestParams,
|
||||
date: requestParams.date,
|
||||
topN: 0,
|
||||
match: "",
|
||||
focusLabel: ""
|
||||
} as CardinalityRequestsParams;
|
||||
};
|
||||
|
||||
const prevDayParams = {
|
||||
...requestParams,
|
||||
date: dayjs(requestParams.date).subtract(1, "day").tz().format(DATE_FORMAT),
|
||||
} as CardinalityRequestsParams;
|
||||
};
|
||||
|
||||
|
||||
const urlBase = getCardinalityInfo(serverUrl, requestParams);
|
||||
const urlPrev = getCardinalityInfo(serverUrl, prevDayParams);
|
||||
const uslTotal = getCardinalityInfo(serverUrl, totalParams);
|
||||
const urls = [urlBase, urlPrev, uslTotal];
|
||||
const urls = [
|
||||
getCardinalityInfo(serverUrl, requestParams),
|
||||
getCardinalityInfo(serverUrl, prevDayParams),
|
||||
getCardinalityInfo(serverUrl, totalParams),
|
||||
];
|
||||
|
||||
try {
|
||||
const responses = await Promise.all(urls.map(url => fetch(url)));
|
||||
const [resp, respPrev, respTotals] = await Promise.all(responses.map(resp => resp.json()));
|
||||
if (responses[0].ok) {
|
||||
const { data: dataTotal } = respTotals;
|
||||
const prevResult = { ...respPrev.data } as TSDBStatus;
|
||||
const result = { ...resp.data } as TSDBStatus;
|
||||
result.totalSeriesByAll = dataTotal?.totalSeries;
|
||||
result.totalSeriesPrev = prevResult?.totalSeries;
|
||||
const [resp, respPrev, respTotals] = await Promise.all(urls.map(getResponseJson));
|
||||
|
||||
const name = match?.replace(/[{}"]/g, "");
|
||||
result.seriesCountByLabelValuePair = result.seriesCountByLabelValuePair.filter(s => s.name !== name);
|
||||
const prevResult = { ...respPrev.data };
|
||||
const { data: dataTotal } = respTotals;
|
||||
const result: TSDBStatus = {
|
||||
...resp.data,
|
||||
totalSeries: resp.data?.totalSeries || resp.data?.headStats?.numSeries || 0,
|
||||
totalLabelValuePairs: resp.data?.totalLabelValuePairs || resp.data?.headStats?.numLabelValuePairs || 0,
|
||||
seriesCountByLabelName: resp.data?.seriesCountByLabelName || [],
|
||||
seriesCountByFocusLabelValue: resp.data?.seriesCountByFocusLabelValue || [],
|
||||
totalSeriesByAll: dataTotal?.totalSeries || dataTotal?.headStats?.numSeries || 0,
|
||||
totalSeriesPrev: prevResult?.totalSeries || prevResult?.headStats?.numSeries || 0,
|
||||
};
|
||||
|
||||
Object.keys(result).forEach(k => {
|
||||
const key = k as keyof TSDBStatus;
|
||||
const entries = result[key];
|
||||
const prevEntries = prevResult[key];
|
||||
const name = match?.replace(/[{}"]/g, "");
|
||||
result.seriesCountByLabelValuePair = result.seriesCountByLabelValuePair.filter(s => s.name !== name);
|
||||
|
||||
if (Array.isArray(entries) && Array.isArray(prevEntries)) {
|
||||
entries.forEach((entry) => {
|
||||
const valuePrev = prevEntries.find(prevEntry => prevEntry.name === entry.name)?.value;
|
||||
entry.diff = valuePrev ? entry.value - valuePrev : 0;
|
||||
entry.valuePrev = valuePrev || 0;
|
||||
});
|
||||
}
|
||||
});
|
||||
calculateDiffs(result, prevResult);
|
||||
|
||||
setTSDBStatus(result);
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
setError(resp.error);
|
||||
setTSDBStatus(appConfigurator.defaultTSDBStatus);
|
||||
setIsLoading(false);
|
||||
}
|
||||
setTSDBStatus(result);
|
||||
setIsLoading(false);
|
||||
} catch (e) {
|
||||
setIsLoading(false);
|
||||
if (e instanceof Error) setError(`${e.name}: ${e.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
fetchCardinalityInfo({ topN, match, date, focusLabel });
|
||||
}, [serverUrl, match, focusLabel, topN, date]);
|
||||
|
|
|
@ -56,6 +56,7 @@ const CardinalityPanel: FC = () => {
|
|||
>
|
||||
{isLoading && <Spinner message={spinnerMessage}/>}
|
||||
<CardinalityConfigurator
|
||||
isPrometheus={appConfigurator.isPrometheusData}
|
||||
totalSeries={tsdbStatusData.totalSeries}
|
||||
totalSeriesPrev={tsdbStatusData.totalSeriesPrev}
|
||||
totalSeriesAll={tsdbStatusData.totalSeriesByAll}
|
||||
|
@ -86,6 +87,7 @@ const CardinalityPanel: FC = () => {
|
|||
totalSeriesPrev={appConfigurator.totalSeries(keyName, true)}
|
||||
totalSeries={appConfigurator.totalSeries(keyName)}
|
||||
tableHeaderCells={tablesHeaders[keyName]}
|
||||
isPrometheus={appConfigurator.isPrometheusData}
|
||||
/>;
|
||||
})}
|
||||
</div>
|
||||
|
|
|
@ -10,6 +10,7 @@ export interface TSDBStatus {
|
|||
seriesCountByFocusLabelValue: TopHeapEntry[];
|
||||
seriesCountByLabelValuePair: TopHeapEntry[];
|
||||
labelValueCountByLabelName: TopHeapEntry[];
|
||||
headStats?: object;
|
||||
}
|
||||
|
||||
export interface TopHeapEntry {
|
||||
|
|
|
@ -31,6 +31,7 @@ The following `tip` changes can be tested by building VictoriaMetrics components
|
|||
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): retry failed write request on the closed connection immediately, without waiting for backoff. This should improve data delivery speed and reduce amount of error logs emitted by vmagent when using idle connections. See related [issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4139).
|
||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): reduces load on Kubernetes control plane during initial service discovery. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4855) for details.
|
||||
* FEATURE: [VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): reduce the maximum recovery time at `vmselect` and `vminsert` when some of `vmstorage` nodes become unavailable because of networking issues from 60 seconds to 3 seconds by default. The recovery time can be tuned at `vmselect` and `vminsert` nodes with `-vmstorageUserTimeout` command-line flag if needed. Thanks to @wjordan for [the pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/4423).
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add Prometheus data support to the "Explore cardinality" page. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4320) for details.
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): make the warning message more noticeable for text fields. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4848).
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add button for auto-formatting PromQL/MetricsQL queries. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4681). Thanks to @aramattamara for the [pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/4694).
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): improve accessibility score to 100 according to [Google's Lighthouse](https://developer.chrome.com/docs/lighthouse/accessibility/) tests.
|
||||
|
|
Loading…
Reference in a new issue