mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2025-01-10 15:14:09 +00:00
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:
parent
82440e76c5
commit
5bc13e2fe8
18 changed files with 368 additions and 264 deletions
|
@ -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"
|
||||
]
|
||||
}
|
|
@ -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
2
app/vmselect/vmui/static/js/main.fdf5a65f.js
Normal file
2
app/vmselect/vmui/static/js/main.fdf5a65f.js
Normal file
File diff suppressed because one or more lines are too long
|
@ -42,7 +42,7 @@ module.exports = {
|
|||
"max-lines": [
|
||||
"error",
|
||||
{
|
||||
"max": 150,
|
||||
"max": 1000,
|
||||
"skipBlankLines": true,
|
||||
"skipComments": true,
|
||||
}
|
||||
|
|
|
@ -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}`;
|
||||
};
|
||||
|
||||
|
|
|
@ -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> "label=value" pairs
|
||||
at <b>{date}</b> {match && <span>for series selector <b>{match}</b></span>}.
|
||||
Show top {topN} entries per table.
|
||||
</Box>
|
||||
</Box>;
|
||||
};
|
||||
|
|
|
@ -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]}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -34,7 +34,7 @@ const MetricsContent: FC<MetricsProperties> = ({
|
|||
tabId,
|
||||
onActionClick,
|
||||
sectionTitle,
|
||||
tableHeaderCells
|
||||
tableHeaderCells,
|
||||
}) => {
|
||||
const tableCells = (row: Data) => (
|
||||
<TableCells
|
||||
|
|
|
@ -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[];
|
|
@ -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,
|
||||
};
|
||||
};
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -17,7 +17,8 @@ const stateToUrlParams = {
|
|||
"topN": "topN",
|
||||
"date": "date",
|
||||
"match": "match[]",
|
||||
"extraLabel": "extra_label"
|
||||
"extraLabel": "extra_label",
|
||||
"focusLabel": "focusLabel"
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
|
Loading…
Reference in a new issue