mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
app/vmui: small fixes
* Remove unneeded dependency on `numeral` package * Properly parse numbers obtained from /api/v1/query_range according to https://prometheus.io/docs/prometheus/latest/querying/api/#expression-query-result-formats * Optimize updating processing the received data from /api/v1/query_range * Make smoother zoom on `ctrl+scroll` * Reduce the number of points received from /api/v1/query_range by 2x in order to reduce load on backend
This commit is contained in:
parent
93c2db5546
commit
1d7c877b7b
13 changed files with 102 additions and 77 deletions
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"files": {
|
||||
"main.css": "./static/css/main.098d452b.css",
|
||||
"main.js": "./static/js/main.c31c0e34.js",
|
||||
"main.js": "./static/js/main.c945b173.js",
|
||||
"static/js/27.939f971b.chunk.js": "./static/js/27.939f971b.chunk.js",
|
||||
"index.html": "./index.html"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.098d452b.css",
|
||||
"static/js/main.c31c0e34.js"
|
||||
"static/js/main.c945b173.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 defer="defer" src="./static/js/main.c31c0e34.js"></script><link href="./static/css/main.098d452b.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 defer="defer" src="./static/js/main.c945b173.js"></script><link href="./static/css/main.098d452b.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.c945b173.js
Normal file
2
app/vmselect/vmui/static/js/main.c945b173.js
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,11 +1,3 @@
|
|||
/*! @preserve
|
||||
* numeral.js
|
||||
* version : 2.0.6
|
||||
* author : Adam Draper
|
||||
* license : MIT
|
||||
* http://adamwdraper.github.com/Numeral-js/
|
||||
*/
|
||||
|
||||
/**
|
||||
* A better abstraction over CSS.
|
||||
*
|
25
app/vmui/packages/vmui/package-lock.json
generated
25
app/vmui/packages/vmui/package-lock.json
generated
|
@ -22,7 +22,6 @@
|
|||
"@types/lodash.get": "^4.4.6",
|
||||
"@types/lodash.throttle": "^4.1.6",
|
||||
"@types/node": "^17.0.17",
|
||||
"@types/numeral": "^2.0.2",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@types/react": "^17.0.39",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
|
@ -31,7 +30,6 @@
|
|||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"numeral": "^2.0.6",
|
||||
"preact": "^10.6.5",
|
||||
"qs": "^6.10.3",
|
||||
"typescript": "~4.5.5",
|
||||
|
@ -4389,11 +4387,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.17.tgz",
|
||||
"integrity": "sha512-e8PUNQy1HgJGV3iU/Bp2+D/DXh3PYeyli8LgIwsQcs1Ar1LoaWHSIT6Rw+H2rNJmiq6SNWiDytfx8+gYj7wDHw=="
|
||||
},
|
||||
"node_modules/@types/numeral": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/numeral/-/numeral-2.0.2.tgz",
|
||||
"integrity": "sha512-A8F30k2gYJ/6e07spSCPpkuZu79LCnkPTvqmIWQzNGcrzwFKpVOydG41lNt5wZXjSI149qjyzC2L1+F2PD/NUA=="
|
||||
},
|
||||
"node_modules/@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
|
@ -13751,14 +13744,6 @@
|
|||
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/numeral": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz",
|
||||
"integrity": "sha1-StCAk21EPCVhrtnyGX7//iX05QY=",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/nwsapi": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz",
|
||||
|
@ -22571,11 +22556,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.17.tgz",
|
||||
"integrity": "sha512-e8PUNQy1HgJGV3iU/Bp2+D/DXh3PYeyli8LgIwsQcs1Ar1LoaWHSIT6Rw+H2rNJmiq6SNWiDytfx8+gYj7wDHw=="
|
||||
},
|
||||
"@types/numeral": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/numeral/-/numeral-2.0.2.tgz",
|
||||
"integrity": "sha512-A8F30k2gYJ/6e07spSCPpkuZu79LCnkPTvqmIWQzNGcrzwFKpVOydG41lNt5wZXjSI149qjyzC2L1+F2PD/NUA=="
|
||||
},
|
||||
"@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
|
@ -29754,11 +29734,6 @@
|
|||
"boolbase": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"numeral": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz",
|
||||
"integrity": "sha1-StCAk21EPCVhrtnyGX7//iX05QY="
|
||||
},
|
||||
"nwsapi": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz",
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
"@types/lodash.get": "^4.4.6",
|
||||
"@types/lodash.throttle": "^4.1.6",
|
||||
"@types/node": "^17.0.17",
|
||||
"@types/numeral": "^2.0.2",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@types/react": "^17.0.39",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
|
@ -27,7 +26,6 @@
|
|||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"numeral": "^2.0.6",
|
||||
"preact": "^10.6.5",
|
||||
"qs": "^6.10.3",
|
||||
"typescript": "~4.5.5",
|
||||
|
|
|
@ -13,6 +13,21 @@ export interface GraphViewProps {
|
|||
data?: MetricResult[];
|
||||
}
|
||||
|
||||
const promValueToNumber = (s: string): number => {
|
||||
// See https://prometheus.io/docs/prometheus/latest/querying/api/#expression-query-result-formats
|
||||
switch (s) {
|
||||
case "NaN":
|
||||
return NaN;
|
||||
case "Inf":
|
||||
case "+Inf":
|
||||
return Infinity;
|
||||
case "-Inf":
|
||||
return -Infinity;
|
||||
default:
|
||||
return parseFloat(s);
|
||||
}
|
||||
};
|
||||
|
||||
const GraphView: FC<GraphViewProps> = ({data = []}) => {
|
||||
const graphDispatch = useGraphDispatch();
|
||||
const {time: {period}} = useAppState();
|
||||
|
@ -43,19 +58,36 @@ const GraphView: FC<GraphViewProps> = ({data = []}) => {
|
|||
const seriesItem = getSeriesItem(d, hideSeries);
|
||||
tempSeries.push(seriesItem);
|
||||
tempLegend.push(getLegendItem(seriesItem, d.group));
|
||||
|
||||
d.values.forEach(v => {
|
||||
let tmpValues = tempValues[d.group];
|
||||
if (!tmpValues) {
|
||||
tmpValues = [];
|
||||
}
|
||||
for (const v of d.values) {
|
||||
tempTimes.push(v[0]);
|
||||
tempValues[d.group] ? tempValues[d.group].push(+v[1]) : tempValues[d.group] = [+v[1]];
|
||||
});
|
||||
tmpValues.push(promValueToNumber(v[1]));
|
||||
}
|
||||
tempValues[d.group] = tmpValues;
|
||||
});
|
||||
|
||||
const timeSeries = getTimeSeries(tempTimes, currentStep, period);
|
||||
setDataChart([timeSeries, ...data.map(d => {
|
||||
return timeSeries.map(t => {
|
||||
const value = d.values.find(v => v[0] === t);
|
||||
return value ? +value[1] : null;
|
||||
});
|
||||
const results = [];
|
||||
const values = d.values;
|
||||
let j = 0;
|
||||
for (const t of timeSeries) {
|
||||
while (j < values.length && values[j][0] < t) j++;
|
||||
let v = null;
|
||||
if (j < values.length && values[j][0] == t) {
|
||||
v = promValueToNumber(values[j][1]);
|
||||
if (!Number.isFinite(v)) {
|
||||
// Treat special values as nulls in order to satisfy uPlot.
|
||||
// Otherwise it may draw unexpected graphs.
|
||||
v = null;
|
||||
}
|
||||
}
|
||||
results.push(v);
|
||||
}
|
||||
return results;
|
||||
})] as uPlotData);
|
||||
setLimitsYaxis(tempValues);
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ const LineChart: FC<LineChartProps> = ({data, series, metrics = []}) => {
|
|||
};
|
||||
|
||||
const onReadyChart = (u: uPlot) => {
|
||||
const factor = 0.85;
|
||||
const factor = 0.9;
|
||||
tooltipOffset.left = parseFloat(u.over.style.left);
|
||||
tooltipOffset.top = parseFloat(u.over.style.top);
|
||||
u.root.querySelector(".u-wrap")?.appendChild(tooltip);
|
||||
|
|
|
@ -1,17 +1,23 @@
|
|||
export const getMaxFromArray = (arr: number[]): number => {
|
||||
let len = arr.length;
|
||||
export const getMaxFromArray = (a: number[]) => {
|
||||
let len = a.length;
|
||||
let max = -Infinity;
|
||||
while (len--) {
|
||||
if (arr[len] > max) max = arr[len];
|
||||
const v = a[len];
|
||||
if (Number.isFinite(v) && v > max) {
|
||||
max = v;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
return Number.isFinite(max) ? max : null;
|
||||
};
|
||||
|
||||
export const getMinFromArray = (arr: number[]): number => {
|
||||
let len = arr.length;
|
||||
export const getMinFromArray = (a: number[]) => {
|
||||
let len = a.length;
|
||||
let min = Infinity;
|
||||
while (len--) {
|
||||
if (arr[len] < min) min = arr[len];
|
||||
const v = a[len];
|
||||
if (Number.isFinite(v) && v < min) {
|
||||
min = v;
|
||||
}
|
||||
}
|
||||
return min;
|
||||
return Number.isFinite(min) ? min : null;
|
||||
};
|
|
@ -2,12 +2,11 @@ import {TimeParams, TimePeriod} from "../types";
|
|||
import dayjs, {UnitTypeShort} from "dayjs";
|
||||
import duration from "dayjs/plugin/duration";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import numeral from "numeral";
|
||||
|
||||
dayjs.extend(duration);
|
||||
dayjs.extend(utc);
|
||||
|
||||
const MAX_ITEMS_PER_CHART = window.innerWidth / 2;
|
||||
const MAX_ITEMS_PER_CHART = window.innerWidth / 4;
|
||||
|
||||
export const limitsDurations = {min: 1, max: 1.578e+11}; // min: 1 ms, max: 5 years
|
||||
|
||||
|
@ -26,7 +25,7 @@ export const supportedDurations = [
|
|||
|
||||
const shortDurations = supportedDurations.map(d => d.short);
|
||||
|
||||
export const roundTimeSeconds = (num: number): number => +(numeral(num).format("0.000"));
|
||||
export const roundToMilliseconds = (num: number): number => Math.round(num*1000)/1000;
|
||||
|
||||
export const isSupportedDuration = (str: string): Partial<Record<UnitTypeShort, string>> | undefined => {
|
||||
|
||||
|
@ -59,7 +58,7 @@ export const getTimeperiodForDuration = (dur: string, date?: Date): TimeParams =
|
|||
}, {});
|
||||
|
||||
const delta = dayjs.duration(durObject).asSeconds();
|
||||
const step = roundTimeSeconds(delta / MAX_ITEMS_PER_CHART) || 0.001;
|
||||
const step = roundToMilliseconds(delta / MAX_ITEMS_PER_CHART) || 0.001;
|
||||
|
||||
return {
|
||||
start: n - delta,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {Axis, Series} from "uplot";
|
||||
import {getMaxFromArray, getMinFromArray} from "../math";
|
||||
import {roundTimeSeconds} from "../time";
|
||||
import {roundToMilliseconds} from "../time";
|
||||
import {AxisRange} from "../../state/graph/reducer";
|
||||
import {formatTicks} from "./helpers";
|
||||
import {TimeParams} from "../../types";
|
||||
|
@ -12,19 +12,37 @@ export const getAxes = (series: Series[]): Axis[] => Array.from(new Set(series.m
|
|||
return axis;
|
||||
});
|
||||
|
||||
export const getTimeSeries = (times: number[], defaultStep: number, period: TimeParams): number[] => {
|
||||
export const getTimeSeries = (times: number[], step: number, period: TimeParams): number[] => {
|
||||
const allTimes = Array.from(new Set(times)).sort((a, b) => a - b);
|
||||
const length = Math.ceil((period.end - period.start)/defaultStep);
|
||||
const startTime = allTimes[0] || 0;
|
||||
return new Array(length*2).fill(startTime).map((d, i) => roundTimeSeconds(d + (defaultStep * i)));
|
||||
let t = period.start;
|
||||
const tEnd = roundToMilliseconds(period.end + step);
|
||||
let j = 0;
|
||||
const results: number[] = [];
|
||||
while (t <= tEnd) {
|
||||
while (j < allTimes.length && allTimes[j] <= t) {
|
||||
t = allTimes[j];
|
||||
j++;
|
||||
results.push(t);
|
||||
}
|
||||
t = roundToMilliseconds(t + step);
|
||||
if (j >= allTimes.length || allTimes[j] > t) {
|
||||
results.push(t);
|
||||
}
|
||||
}
|
||||
while (results.length < 2) {
|
||||
results.push(t);
|
||||
t = roundToMilliseconds(t + step);
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
export const getMinMaxBuffer = (min: number, max: number): [number, number] => {
|
||||
const minCorrect = isNaN(min) ? -1 : min;
|
||||
const maxCorrect = isNaN(max) ? 1 : max;
|
||||
const valueRange = Math.abs(maxCorrect - minCorrect) || Math.abs(minCorrect) || 1;
|
||||
export const getMinMaxBuffer = (min: number | null, max: number | null): [number, number] => {
|
||||
if (min == null || max == null) {
|
||||
return [-1, 1];
|
||||
}
|
||||
const valueRange = Math.abs(max - min) || Math.abs(min) || 1;
|
||||
const padding = 0.02*valueRange;
|
||||
return [minCorrect - padding, maxCorrect + padding];
|
||||
return [min - padding, max + padding];
|
||||
};
|
||||
|
||||
export const getLimitsYAxis = (values: { [key: string]: number[] }): AxisRange => {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import uPlot from "uplot";
|
||||
import numeral from "numeral";
|
||||
import {getColorFromString} from "../color";
|
||||
|
||||
export const defaultOptions = {
|
||||
|
@ -29,8 +28,14 @@ export const defaultOptions = {
|
|||
},
|
||||
};
|
||||
|
||||
export const formatTicks = (u: uPlot, ticks: number[]): (string | number)[] => {
|
||||
return ticks.map(n => n > 1000 ? numeral(n).format("0.0a") : n);
|
||||
export const formatTicks = (u: uPlot, ticks: number[]): string[] => {
|
||||
return ticks.map(v => {
|
||||
const n = Math.abs(v);
|
||||
if (n > 1e-3 && n < 1e4) {
|
||||
return v.toString();
|
||||
}
|
||||
return v.toExponential(1);
|
||||
});
|
||||
};
|
||||
|
||||
export const getColorLine = (scale: number, label: string): string => getColorFromString(`${scale}${label}`);
|
||||
|
|
Loading…
Reference in a new issue