vmui: added focusLabel, enable cardinality app configuratior (#2736)

* vmui: added focusLabel, enable app configuratior

* vmui: set focusLabel if {labelName!=""}

* wip

* docs/CHANGELOG.md: mention about focusLabel feature in cardinality explorer

Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2730

Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
This commit is contained in:
Dmytro Kozlov 2022-06-17 13:03:02 +03:00 committed by Aliaksandr Valialkin
parent 82440e76c5
commit 5bc13e2fe8
No known key found for this signature in database
GPG key ID: A72BEC6CD3D0DED1
18 changed files with 368 additions and 264 deletions

View file

@ -1,12 +1,12 @@
{
"files": {
"main.css": "./static/css/main.7e6d0c89.css",
"main.js": "./static/js/main.f7185a13.js",
"main.js": "./static/js/main.fdf5a65f.js",
"static/js/27.939f971b.chunk.js": "./static/js/27.939f971b.chunk.js",
"index.html": "./index.html"
},
"entrypoints": [
"static/css/main.7e6d0c89.css",
"static/js/main.f7185a13.js"
"static/js/main.fdf5a65f.js"
]
}

View file

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="VM-UI is a metric explorer for Victoria Metrics"/><link rel="apple-touch-icon" href="./apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png"><link rel="manifest" href="./manifest.json"/><title>VM UI</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><script src="./dashboards/index.js" type="module"></script><script defer="defer" src="./static/js/main.f7185a13.js"></script><link href="./static/css/main.7e6d0c89.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="VM-UI is a metric explorer for Victoria Metrics"/><link rel="apple-touch-icon" href="./apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png"><link rel="manifest" href="./manifest.json"/><title>VM UI</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><script src="./dashboards/index.js" type="module"></script><script defer="defer" src="./static/js/main.fdf5a65f.js"></script><link href="./static/css/main.7e6d0c89.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -42,7 +42,7 @@ module.exports = {
"max-lines": [
"error",
{
"max": 150,
"max": 1000,
"skipBlankLines": true,
"skipComments": true,
}

View file

@ -3,10 +3,12 @@ export interface CardinalityRequestsParams {
extraLabel: string | null,
match: string | null,
date: string | null,
focusLabel: string | null,
}
export const getCardinalityInfo = (server: string, requestsParam: CardinalityRequestsParams) => {
const match = requestsParam.match ? `&match[]=${requestsParam.match}` : "";
return `${server}/api/v1/status/tsdb?topN=${requestsParam.topN}&date=${requestsParam.date}${match}`;
const match = requestsParam.match ? "&match[]=" + encodeURIComponent(requestsParam.match) : "";
const focusLabel = requestsParam.focusLabel ? "&focusLabel=" + encodeURIComponent(requestsParam.focusLabel) : "";
return `${server}/api/v1/status/tsdb?topN=${requestsParam.topN}&date=${requestsParam.date}${match}${focusLabel}`;
};

View file

@ -17,6 +17,7 @@ export interface CardinalityConfiguratorProps {
onSetQuery: (query: string, index: number) => void;
onRunQuery: () => void;
onTopNChange: (e: ChangeEvent<HTMLTextAreaElement|HTMLInputElement>) => void;
onFocusLabelChange: (e: ChangeEvent<HTMLTextAreaElement|HTMLInputElement>) => void;
query: string;
topN: number;
error?: ErrorTypes | string;
@ -24,6 +25,7 @@ export interface CardinalityConfiguratorProps {
totalLabelValuePairs: number;
date: string | null;
match: string | null;
focusLabel: string | null;
}
const CardinalityConfigurator: FC<CardinalityConfiguratorProps> = ({
@ -34,10 +36,12 @@ const CardinalityConfigurator: FC<CardinalityConfiguratorProps> = ({
onRunQuery,
onSetQuery,
onTopNChange,
onFocusLabelChange,
totalSeries,
totalLabelValuePairs,
date,
match
match,
focusLabel
}) => {
const dispatch = useAppDispatch();
const {queryControls: {autocomplete}} = useAppState();
@ -50,40 +54,48 @@ const CardinalityConfigurator: FC<CardinalityConfiguratorProps> = ({
return <Box boxShadow="rgba(99, 99, 99, 0.2) 0px 2px 8px 0px;" p={4} pb={2} mb={2}>
<Box>
<Box display="grid" gridTemplateColumns="1fr auto auto" gap="4px" width="50%" mb={4}>
<Box display="grid" gridTemplateColumns="1fr auto auto auto auto" gap="4px" width="100%" mb={4}>
<QueryEditor
query={query} index={0} autocomplete={autocomplete} queryOptions={queryOptions}
error={error} setHistoryIndex={onSetHistory} runQuery={onRunQuery} setQuery={onSetQuery}
label={"Time series selector"}
/>
<Box display="flex" alignItems="center">
<Box ml={2}>
<TextField
label="Number of entries per table"
type="number"
size="small"
variant="outlined"
value={topN}
error={topN < 1}
helperText={topN < 1 ? "Number must be bigger than zero" : " "}
onChange={onTopNChange}/>
</Box>
<Tooltip title="Execute Query">
<IconButton onClick={onRunQuery} sx={{height: "49px", width: "49px"}}>
<PlayCircleOutlineIcon/>
</IconButton>
</Tooltip>
<Box>
<FormControlLabel label="Enable autocomplete"
control={<BasicSwitch checked={autocomplete} onChange={onChangeAutocomplete}/>}
/>
</Box>
<Box mr={2}>
<TextField
label="Number of entries per table"
type="number"
size="medium"
variant="outlined"
value={topN}
error={topN < 1}
helperText={topN < 1 ? "Number must be bigger than zero" : " "}
onChange={onTopNChange}/>
</Box>
<Box mr={2}>
<TextField
label="Focus label"
type="text"
size="medium"
variant="outlined"
value={focusLabel}
onChange={onFocusLabelChange} />
</Box>
<Box>
<FormControlLabel label="Enable autocomplete"
control={<BasicSwitch checked={autocomplete} onChange={onChangeAutocomplete}/>}
/>
</Box>
<Tooltip title="Execute Query">
<IconButton onClick={onRunQuery} sx={{height: "49px", width: "49px"}}>
<PlayCircleOutlineIcon/>
</IconButton>
</Tooltip>
</Box>
</Box>
<Box>
Analyzed <b>{totalSeries}</b> series with <b>{totalLabelValuePairs}</b> label=value pairs
at <b>{date}</b> {match && <span>for series selector <b>{match}</b></span>}. Show top {topN} entries per table.
Analyzed <b>{totalSeries}</b> series with <b>{totalLabelValuePairs}</b> &quot;label=value&quot; pairs
at <b>{date}</b> {match && <span>for series selector <b>{match}</b></span>}.
Show top {topN} entries per table.
</Box>
</Box>;
};

View file

@ -2,24 +2,30 @@ import React, {ChangeEvent, FC, useState} from "react";
import {SyntheticEvent} from "react";
import {Alert} from "@mui/material";
import {useFetchQuery} from "../../hooks/useCardinalityFetch";
import {
METRIC_NAMES_HEADERS,
LABEL_NAMES_HEADERS,
LABEL_VALUE_PAIRS_HEADERS,
LABELS_WITH_UNIQUE_VALUES_HEADERS,
spinnerContainerStyles
} from "./consts";
import {defaultProperties, queryUpdater} from "./helpers";
import {queryUpdater} from "./helpers";
import {Data} from "../Table/types";
import CardinalityConfigurator from "./CardinalityConfigurator/CardinalityConfigurator";
import Spinner from "../common/Spinner";
import {useCardinalityDispatch, useCardinalityState} from "../../state/cardinality/CardinalityStateContext";
import MetricsContent from "./MetricsContent/MetricsContent";
import {DefaultActiveTab, Tabs, TSDBStatus, Containers} from "./types";
const spinnerContainerStyles = (height: string) => {
return {
width: "100%",
maxWidth: "100%",
position: "absolute",
height: height ?? "50%",
background: "rgba(255, 255, 255, 0.7)",
pointerEvents: "none",
zIndex: 1000,
};
};
const CardinalityPanel: FC = () => {
const cardinalityDispatch = useCardinalityDispatch();
const {topN, match, date} = useCardinalityState();
const {topN, match, date, focusLabel} = useCardinalityState();
const configError = "";
const [query, setQuery] = useState(match || "");
const [queryHistoryIndex, setQueryHistoryIndex] = useState(0);
@ -47,10 +53,13 @@ const CardinalityPanel: FC = () => {
cardinalityDispatch({type: "SET_TOP_N", payload: +e.target.value});
};
const {isLoading, tsdbStatus, error} = useFetchQuery();
const defaultProps = defaultProperties(tsdbStatus);
const [stateTabs, setTab] = useState(defaultProps.defaultState);
const onFocusLabelChange = (e: ChangeEvent<HTMLTextAreaElement|HTMLInputElement>) => {
cardinalityDispatch({type: "SET_FOCUS_LABEL", payload: e.target.value});
};
const {isLoading, appConfigurator, error} = useFetchQuery();
const [stateTabs, setTab] = useState(appConfigurator.defaultState.defaultActiveTab);
const {tsdbStatusData, defaultState, tablesHeaders} = appConfigurator;
const handleTabChange = (e: SyntheticEvent, newValue: number) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
@ -59,11 +68,16 @@ const CardinalityPanel: FC = () => {
const handleFilterClick = (key: string) => (e: SyntheticEvent) => {
const name = e.currentTarget.id;
const query = queryUpdater[key](name);
const query = queryUpdater[key](focusLabel, name);
setQuery(query);
setQueryHistory(prev => [...prev, query]);
setQueryHistoryIndex(prev => prev + 1);
cardinalityDispatch({type: "SET_MATCH", payload: query});
let newFocusLabel = "";
if (key === "labelValueCountByLabelName" || key == "seriesCountByLabelName") {
newFocusLabel = name;
}
cardinalityDispatch({type: "SET_FOCUS_LABEL", payload: newFocusLabel});
cardinalityDispatch({type: "RUN_QUERY"});
};
@ -79,56 +93,25 @@ const CardinalityPanel: FC = () => {
/>}
<CardinalityConfigurator error={configError} query={query} onRunQuery={onRunQuery} onSetQuery={onSetQuery}
onSetHistory={onSetHistory} onTopNChange={onTopNChange} topN={topN} date={date} match={match}
totalSeries={tsdbStatus.totalSeries} totalLabelValuePairs={tsdbStatus.totalLabelValuePairs}/>
totalSeries={tsdbStatusData.totalSeries} totalLabelValuePairs={tsdbStatusData.totalLabelValuePairs}
focusLabel={focusLabel} onFocusLabelChange={onFocusLabelChange}
/>
{error && <Alert color="error" severity="error" sx={{whiteSpace: "pre-wrap", mt: 2}}>{error}</Alert>}
<MetricsContent
sectionTitle={"Metric names with the highest number of series"}
activeTab={stateTabs.seriesCountByMetricName}
rows={tsdbStatus.seriesCountByMetricName as unknown as Data[]}
onChange={handleTabChange}
onActionClick={handleFilterClick("seriesCountByMetricName")}
tabs={defaultProps.tabs.seriesCountByMetricName}
chartContainer={defaultProps.containerRefs.seriesCountByMetricName}
totalSeries={tsdbStatus.totalSeries}
tabId={"seriesCountByMetricName"}
tableHeaderCells={METRIC_NAMES_HEADERS}
/>
<MetricsContent
sectionTitle={"Labels with the highest number of series"}
activeTab={stateTabs.seriesCountByLabelName}
rows={tsdbStatus.seriesCountByLabelName as unknown as Data[]}
onChange={handleTabChange}
onActionClick={handleFilterClick("seriesCountByLabelName")}
tabs={defaultProps.tabs.seriesCountByLabelName}
chartContainer={defaultProps.containerRefs.seriesCountByLabelName}
totalSeries={tsdbStatus.totalSeries}
tabId={"seriesCountByLabelName"}
tableHeaderCells={LABEL_NAMES_HEADERS}
/>
<MetricsContent
sectionTitle={"Label=value pairs with the highest number of series"}
activeTab={stateTabs.seriesCountByLabelValuePair}
rows={tsdbStatus.seriesCountByLabelValuePair as unknown as Data[]}
onChange={handleTabChange}
onActionClick={handleFilterClick("seriesCountByLabelValuePair")}
tabs={defaultProps.tabs.seriesCountByLabelValuePair}
chartContainer={defaultProps.containerRefs.seriesCountByLabelValuePair}
totalSeries={tsdbStatus.totalSeries}
tabId={"seriesCountByLabelValuePair"}
tableHeaderCells={LABEL_VALUE_PAIRS_HEADERS}
/>
<MetricsContent
sectionTitle={"Labels with the highest number of unique values"}
activeTab={stateTabs.labelValueCountByLabelName}
rows={tsdbStatus.labelValueCountByLabelName as unknown as Data[]}
onChange={handleTabChange}
onActionClick={handleFilterClick("labelValueCountByLabelName")}
tabs={defaultProps.tabs.labelValueCountByLabelName}
chartContainer={defaultProps.containerRefs.labelValueCountByLabelName}
totalSeries={-1}
tabId={"labelValueCountByLabelName"}
tableHeaderCells={LABELS_WITH_UNIQUE_VALUES_HEADERS}
/>
{appConfigurator.keys(focusLabel).map((keyName) => (
<MetricsContent
key={keyName}
sectionTitle={appConfigurator.sectionsTitles(focusLabel)[keyName]}
activeTab={stateTabs[keyName as keyof DefaultActiveTab]}
rows={tsdbStatusData[keyName as keyof TSDBStatus] as unknown as Data[]}
onChange={handleTabChange}
onActionClick={handleFilterClick(keyName)}
tabs={defaultState.tabs[keyName as keyof Tabs]}
chartContainer={defaultState.containerRefs[keyName as keyof Containers<HTMLDivElement>]}
totalSeries={appConfigurator.totalSeries(keyName)}
tabId={keyName}
tableHeaderCells={tablesHeaders[keyName]}
/>
))}
</>
);
};

View file

@ -34,7 +34,7 @@ const MetricsContent: FC<MetricsProperties> = ({
tabId,
onActionClick,
sectionTitle,
tableHeaderCells
tableHeaderCells,
}) => {
const tableCells = (row: Data) => (
<TableCells

View file

@ -0,0 +1,233 @@
import {Containers, DefaultActiveTab, Tabs, TSDBStatus} from "./types";
import {useRef} from "preact/compat";
import {HeadCell} from "../Table/types";
interface AppState {
tabs: Tabs;
containerRefs: Containers<HTMLDivElement>;
defaultActiveTab: DefaultActiveTab,
}
export default class AppConfigurator {
private tsdbStatus: TSDBStatus;
private tabsNames: string[];
constructor() {
this.tsdbStatus = this.defaultTSDBStatus;
this.tabsNames = ["table", "graph"];
}
set tsdbStatusData(tsdbStatus: TSDBStatus) {
this.tsdbStatus = tsdbStatus;
}
get tsdbStatusData(): TSDBStatus {
return this.tsdbStatus;
}
get defaultTSDBStatus(): TSDBStatus {
return {
totalSeries: 0,
totalLabelValuePairs: 0,
seriesCountByMetricName: [],
seriesCountByLabelName: [],
seriesCountByFocusLabelValue: [],
seriesCountByLabelValuePair: [],
labelValueCountByLabelName: [],
};
}
keys(focusLabel: string | null): string[] {
let keys: string[] = [];
if (focusLabel) {
keys = keys.concat("seriesCountByFocusLabelValue");
}
keys = keys.concat(
"seriesCountByMetricName",
"seriesCountByLabelName",
"seriesCountByLabelValuePair",
"labelValueCountByLabelName",
);
return keys;
}
get defaultState(): AppState {
return this.keys("job").reduce((acc, cur) => {
return {
...acc,
tabs: {
...acc.tabs,
[cur]: this.tabsNames,
},
containerRefs: {
...acc.containerRefs,
[cur]: useRef<HTMLDivElement>(null),
},
defaultActiveTab: {
...acc.defaultActiveTab,
[cur]: 0,
},
};
}, {
tabs: {} as Tabs,
containerRefs: {} as Containers<HTMLDivElement>,
defaultActiveTab: {} as DefaultActiveTab,
} as AppState);
}
sectionsTitles(str: string | null): Record<string, string> {
return {
seriesCountByMetricName: "Metric names with the highest number of series",
seriesCountByLabelName: "Labels with the highest number of series",
seriesCountByFocusLabelValue: `Values for "${str}" label with the highest number of series`,
seriesCountByLabelValuePair: "Label=value pairs with the highest number of series",
labelValueCountByLabelName: "Labels with the highest number of unique values",
};
}
get tablesHeaders(): Record<string, HeadCell[]> {
return {
seriesCountByMetricName: METRIC_NAMES_HEADERS,
seriesCountByLabelName: LABEL_NAMES_HEADERS,
seriesCountByFocusLabelValue: FOCUS_LABEL_VALUES_HEADERS,
seriesCountByLabelValuePair: LABEL_VALUE_PAIRS_HEADERS,
labelValueCountByLabelName: LABEL_NAMES_WITH_UNIQUE_VALUES_HEADERS,
};
}
totalSeries(keyName: string): number {
if (keyName === "labelValueCountByLabelName") {
return -1;
}
return this.tsdbStatus.totalSeries;
}
}
const METRIC_NAMES_HEADERS = [
{
disablePadding: false,
id: "name",
label: "Metric name",
numeric: false,
},
{
disablePadding: false,
id: "value",
label: "Number of series",
numeric: false,
},
{
disablePadding: false,
id: "percentage",
label: "Percent of series",
numeric: false,
},
{
disablePadding: false,
id: "action",
label: "Action",
numeric: false,
}
] as HeadCell[];
const LABEL_NAMES_HEADERS = [
{
disablePadding: false,
id: "name",
label: "Label name",
numeric: false,
},
{
disablePadding: false,
id: "value",
label: "Number of series",
numeric: false,
},
{
disablePadding: false,
id: "percentage",
label: "Percent of series",
numeric: false,
},
{
disablePadding: false,
id: "action",
label: "Action",
numeric: false,
}
] as HeadCell[];
const FOCUS_LABEL_VALUES_HEADERS = [
{
disablePadding: false,
id: "name",
label: "Label value",
numeric: false,
},
{
disablePadding: false,
id: "value",
label: "Number of series",
numeric: false,
},
{
disablePadding: false,
id: "percentage",
label: "Percent of series",
numeric: false,
},
{
disablePadding: false,
id: "action",
label: "Action",
numeric: false,
}
] as HeadCell[];
export const LABEL_VALUE_PAIRS_HEADERS = [
{
disablePadding: false,
id: "name",
label: "Label=value pair",
numeric: false,
},
{
disablePadding: false,
id: "value",
label: "Number of series",
numeric: false,
},
{
disablePadding: false,
id: "percentage",
label: "Percent of series",
numeric: false,
},
{
disablePadding: false,
id: "action",
label: "Action",
numeric: false,
}
] as HeadCell[];
export const LABEL_NAMES_WITH_UNIQUE_VALUES_HEADERS = [
{
disablePadding: false,
id: "name",
label: "Label name",
numeric: false,
},
{
disablePadding: false,
id: "value",
label: "Number of unique values",
numeric: false,
},
{
disablePadding: false,
id: "action",
label: "Action",
numeric: false,
}
] as HeadCell[];

View file

@ -1,115 +0,0 @@
import {HeadCell} from "../Table/types";
export const METRIC_NAMES_HEADERS = [
{
disablePadding: false,
id: "name",
label: "Metric name",
numeric: false,
},
{
disablePadding: false,
id: "value",
label: "Number of series",
numeric: false,
},
{
disablePadding: false,
id: "percentage",
label: "Percent of series",
numeric: false,
},
{
disablePadding: false,
id: "action",
label: "Action",
numeric: false,
}
] as HeadCell[];
export const LABEL_NAMES_HEADERS = [
{
disablePadding: false,
id: "name",
label: "Label name",
numeric: false,
},
{
disablePadding: false,
id: "value",
label: "Number of series",
numeric: false,
},
{
disablePadding: false,
id: "percentage",
label: "Percent of series",
numeric: false,
},
{
disablePadding: false,
id: "action",
label: "Action",
numeric: false,
}
] as HeadCell[];
export const LABEL_VALUE_PAIRS_HEADERS = [
{
disablePadding: false,
id: "name",
label: "Label=value pair",
numeric: false,
},
{
disablePadding: false,
id: "value",
label: "Number of series",
numeric: false,
},
{
disablePadding: false,
id: "percentage",
label: "Percent of series",
numeric: false,
},
{
disablePadding: false,
id: "action",
label: "Action",
numeric: false,
}
]as HeadCell[];
export const LABELS_WITH_UNIQUE_VALUES_HEADERS = [
{
disablePadding: false,
id: "name",
label: "Label name",
numeric: false,
},
{
disablePadding: false,
id: "value",
label: "Number of unique values",
numeric: false,
},
{
disablePadding: false,
id: "action",
label: "Action",
numeric: false,
}
] as HeadCell[];
export const spinnerContainerStyles = (height: string) => {
return {
width: "100%",
maxWidth: "100%",
position: "absolute",
height: height ?? "50%",
background: "rgba(255, 255, 255, 0.7)",
pointerEvents: "none",
zIndex: 1000,
};
};

View file

@ -1,45 +1,25 @@
import {Containers, DefaultState, QueryUpdater, Tabs, TSDBStatus} from "./types";
import {useRef} from "preact/compat";
import {QueryUpdater} from "./types";
export const queryUpdater: QueryUpdater = {
seriesCountByMetricName: (query: string): string => {
seriesCountByMetricName: (focusLabel: string | null, query: string): string => {
return getSeriesSelector("__name__", query);
},
seriesCountByLabelName: (query: string): string => `{${query}!=""}`,
seriesCountByLabelValuePair: (query: string): string => {
seriesCountByLabelName: (focusLabel: string | null, query: string): string => `{${query}!=""}`,
seriesCountByFocusLabelValue: (focusLabel: string | null, query: string): string => {
return getSeriesSelector(focusLabel, query);
},
seriesCountByLabelValuePair: (focusLabel: string | null, query: string): string => {
const a = query.split("=");
const label = a[0];
const value = a.slice(1).join("=");
return getSeriesSelector(label, value);
},
labelValueCountByLabelName: (query: string): string => `{${query}!=""}`,
labelValueCountByLabelName: (focusLabel: string | null, query: string): string => `{${query}!=""}`,
};
const getSeriesSelector = (label: string, value: string): string => {
const getSeriesSelector = (label: string | null, value: string): string => {
if (!label) {
return "";
}
return "{" + label + "=" + JSON.stringify(value) + "}";
};
export const defaultProperties = (tsdbStatus: TSDBStatus) => {
return Object.keys(tsdbStatus).reduce((acc, key) => {
if (key === "totalSeries" || key === "totalLabelValuePairs") return acc;
return {
...acc,
tabs:{
...acc.tabs,
[key]: ["table", "graph"],
},
containerRefs: {
...acc.containerRefs,
[key]: useRef<HTMLDivElement>(null),
},
defaultState: {
...acc.defaultState,
[key]: 0,
},
};
}, {
tabs:{} as Tabs,
containerRefs: {} as Containers<HTMLDivElement>,
defaultState: {} as DefaultState,
});
};

View file

@ -5,6 +5,7 @@ export interface TSDBStatus {
totalLabelValuePairs: number;
seriesCountByMetricName: TopHeapEntry[];
seriesCountByLabelName: TopHeapEntry[];
seriesCountByFocusLabelValue: TopHeapEntry[];
seriesCountByLabelValuePair: TopHeapEntry[];
labelValueCountByLabelName: TopHeapEntry[];
}
@ -15,12 +16,13 @@ export interface TopHeapEntry {
}
export type QueryUpdater = {
[key: string]: (query: string) => string,
[key: string]: (focusLabel: string | null, query: string) => string,
}
export interface Tabs {
seriesCountByMetricName: string[];
seriesCountByLabelName: string[];
seriesCountByFocusLabelValue: string[];
seriesCountByLabelValuePair: string[];
labelValueCountByLabelName: string[];
}
@ -28,13 +30,15 @@ export interface Tabs {
export interface Containers<T> {
seriesCountByMetricName: MutableRef<T>;
seriesCountByLabelName: MutableRef<T>;
seriesCountByFocusLabelValue: MutableRef<T>;
seriesCountByLabelValuePair: MutableRef<T>;
labelValueCountByLabelName: MutableRef<T>;
}
export interface DefaultState {
export interface DefaultActiveTab {
seriesCountByMetricName: number;
seriesCountByLabelName: number;
seriesCountByFocusLabelValue: number;
seriesCountByLabelValuePair: number;
labelValueCountByLabelName: number;
}

View file

@ -5,34 +5,28 @@ import {CardinalityRequestsParams, getCardinalityInfo} from "../api/tsdb";
import {getAppModeEnable, getAppModeParams} from "../utils/app-mode";
import {TSDBStatus} from "../components/CardinalityPanel/types";
import {useCardinalityState} from "../state/cardinality/CardinalityStateContext";
import AppConfigurator from "../components/CardinalityPanel/appConfigurator";
const appModeEnable = getAppModeEnable();
const {serverURL: appServerUrl} = getAppModeParams();
const defaultTSDBStatus = {
totalSeries: 0,
totalLabelValuePairs: 0,
seriesCountByMetricName: [],
seriesCountByLabelName: [],
seriesCountByLabelValuePair: [],
labelValueCountByLabelName: [],
};
export const useFetchQuery = (): {
fetchUrl?: string[],
isLoading: boolean,
error?: ErrorTypes | string
tsdbStatus: TSDBStatus,
appConfigurator: AppConfigurator,
} => {
const {topN, extraLabel, match, date, runQuery} = useCardinalityState();
const appConfigurator = new AppConfigurator();
const {topN, extraLabel, match, date, runQuery, focusLabel} = useCardinalityState();
const {serverUrl} = useAppState();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<ErrorTypes | string>();
const [tsdbStatus, setTSDBStatus] = useState<TSDBStatus>(defaultTSDBStatus);
const [tsdbStatus, setTSDBStatus] = useState<TSDBStatus>(appConfigurator.defaultTSDBStatus);
useEffect(() => {
if (error) {
setTSDBStatus(defaultTSDBStatus);
setTSDBStatus(appConfigurator.defaultTSDBStatus);
setIsLoading(false);
}
}, [error]);
@ -42,7 +36,7 @@ export const useFetchQuery = (): {
if (!server) return;
setError("");
setIsLoading(true);
setTSDBStatus(defaultTSDBStatus);
setTSDBStatus(appConfigurator.defaultTSDBStatus);
const url = getCardinalityInfo(server, requestParams);
try {
@ -54,7 +48,7 @@ export const useFetchQuery = (): {
setIsLoading(false);
} else {
setError(resp.error);
setTSDBStatus(defaultTSDBStatus);
setTSDBStatus(appConfigurator.defaultTSDBStatus);
setIsLoading(false);
}
} catch (e) {
@ -65,8 +59,9 @@ export const useFetchQuery = (): {
useEffect(() => {
fetchCardinalityInfo({topN, extraLabel, match, date});
fetchCardinalityInfo({topN, extraLabel, match, date, focusLabel});
}, [serverUrl, runQuery, date]);
return {isLoading, tsdbStatus, error};
appConfigurator.tsdbStatusData = tsdbStatus;
return {isLoading, appConfigurator: appConfigurator, error};
};

View file

@ -7,6 +7,7 @@ export interface CardinalityState {
date: string | null
match: string | null
extraLabel: string | null
focusLabel: string | null
}
export type Action =
@ -14,12 +15,15 @@ export type Action =
| { type: "SET_DATE", payload: string | null }
| { type: "SET_MATCH", payload: string | null }
| { type: "SET_EXTRA_LABEL", payload: string | null }
| { type: "SET_FOCUS_LABEL", payload: string | null }
| { type: "RUN_QUERY" }
export const initialState: CardinalityState = {
runQuery: 0,
topN: getQueryStringValue("topN", 10) as number,
date: getQueryStringValue("date", dayjs(new Date()).format("YYYY-MM-DD")) as string,
focusLabel: getQueryStringValue("focusLabel", "") as string,
match: (getQueryStringValue("match", []) as string[]).join("&"),
extraLabel: getQueryStringValue("extra_label", "") as string,
};
@ -46,6 +50,11 @@ export function reducer(state: CardinalityState, action: Action): CardinalitySta
...state,
extraLabel: action.payload
};
case "SET_FOCUS_LABEL":
return {
...state,
focusLabel: action.payload,
};
case "RUN_QUERY":
return {
...state,

View file

@ -17,7 +17,8 @@ const stateToUrlParams = {
"topN": "topN",
"date": "date",
"match": "match[]",
"extraLabel": "extra_label"
"extraLabel": "extra_label",
"focusLabel": "focusLabel"
}
};

View file

@ -18,7 +18,7 @@ The following tip changes can be tested by building VictoriaMetrics components f
**Update notes:** this release introduces backwards-incompatible changes to communication protocol between `vmselect` and `vmstorage` nodes in cluster version of VictoriaMetrics because of added [query tracing](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#query-tracing), so `vmselect` and `vmstorage` nodes may log communication errors during the upgrade. These errors should stop after all the `vmselect` and `vmstorage` nodes are updated to new release. It is safe to downgrade to previous releases.
* FEATURE: support query tracing, which allows determining bottlenecks during query processing. See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#query-tracing) and [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1403).
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add `cardinality` tab, which can help identifying the source of [high cardinality](https://docs.victoriametrics.com/FAQ.html#what-is-high-cardinality) and [high churn rate](https://docs.victoriametrics.com/FAQ.html#what-is-high-churn-rate) issues. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2233) and [these docs](https://docs.victoriametrics.com/#cardinality-explorer).
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add `cardinality` tab, which can help identifying the source of [high cardinality](https://docs.victoriametrics.com/FAQ.html#what-is-high-cardinality) and [high churn rate](https://docs.victoriametrics.com/FAQ.html#what-is-high-churn-rate) issues. See [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2233) and [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2730) feature requests and [these docs](https://docs.victoriametrics.com/#cardinality-explorer).
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): small UX enhancements according to [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2638).
* FEATURE: allow overriding default limits for in-memory cache `indexdb/tagFilters` via flag `-storage.cacheSizeIndexDBTagFilters`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2663).
* FEATURE: add support of `lowercase` and `uppercase` relabeling actions in the same way as [Prometheus 2.36.0 does](https://github.com/prometheus/prometheus/releases/tag/v2.36.0). See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2664).