Merge branch 'public-single-node' into pmm-6401-read-prometheus-data-files

This commit is contained in:
Aliaksandr Valialkin 2022-10-26 14:57:17 +03:00
commit 70bcc97d1c
No known key found for this signature in database
GPG key ID: A72BEC6CD3D0DED1
30 changed files with 309 additions and 77 deletions

View file

@ -132,7 +132,15 @@ VictoriaMetrics is developed at a fast pace, so it is recommended periodically c
### Environment variables ### Environment variables
Each flag value can be set via environment variables according to these rules: All the VictoriaMetrics components allow referring environment variables in command-line flags via `%{ENV_VAR}` syntax.
For example, `-metricsAuthKey=%{METRICS_AUTH_KEY}` is automatically expanded to `-metricsAuthKey=top-secret`
if `METRICS_AUTH_KEY=top-secret` environment variable exists at VictoriaMetrics startup.
This expansion is performed by VictoriaMetrics itself.
VictoriaMetrics recursively expands `%{ENV_VAR}` references in environment variables on startup.
For example, `FOO=%{BAR}` environment variable is expanded to `FOO=abc` if `BAR=a%{BAZ}` and `BAZ=bc`.
Additionally, all the VictoriaMetrics components allow setting flag values via environment variables according to these rules:
* The `-envflag.enable` flag must be set. * The `-envflag.enable` flag must be set.
* Each `.` char in flag name must be substituted with `_` (for example `-insert.maxQueueDuration <duration>` will translate to `insert_maxQueueDuration=<duration>`). * Each `.` char in flag name must be substituted with `_` (for example `-insert.maxQueueDuration <duration>` will translate to `insert_maxQueueDuration=<duration>`).
@ -277,7 +285,7 @@ Multi-line queries can be entered by pressing `Shift-Enter` in query input field
When querying the [backfilled data](https://docs.victoriametrics.com/#backfilling) or during [query troubleshooting](https://docs.victoriametrics.com/Troubleshooting.html#unexpected-query-results), it may be useful disabling response cache by clicking `Disable cache` checkbox. When querying the [backfilled data](https://docs.victoriametrics.com/#backfilling) or during [query troubleshooting](https://docs.victoriametrics.com/Troubleshooting.html#unexpected-query-results), it may be useful disabling response cache by clicking `Disable cache` checkbox.
VMUI automatically adjusts the interval between datapoints on the graph depending on the horizontal resolution and on the selected time range. The step value can be customized by clickhing `Override step value` checkbox. VMUI automatically adjusts the interval between datapoints on the graph depending on the horizontal resolution and on the selected time range. The step value can be customized by changing `Step value` input.
VMUI allows investigating correlations between multiple queries on the same graph. Just click `Add Query` button, enter an additional query in the newly appeared input field and press `Ctrl+Enter`. Results for all the queries should be displayed simultaneously on the same graph. VMUI allows investigating correlations between multiple queries on the same graph. Just click `Add Query` button, enter an additional query in the newly appeared input field and press `Ctrl+Enter`. Results for all the queries should be displayed simultaneously on the same graph.
@ -321,7 +329,7 @@ VictoriaMetrics is configured via command-line flags, so it must be restarted wh
* Wait until the process stops. This can take a few seconds. * Wait until the process stops. This can take a few seconds.
* Start VictoriaMetrics with the new command-line flags. * Start VictoriaMetrics with the new command-line flags.
Prometheus doesn't drop data during VictoriaMetrics restart. See [this article](https://grafana.com/blog/2019/03/25/whats-new-in-prometheus-2.8-wal-based-remote-write/) for details. The same applies alos to [vmagent](https://docs.victoriametrics.com/vmagent.html). Prometheus doesn't drop data during VictoriaMetrics restart. See [this article](https://grafana.com/blog/2019/03/25/whats-new-in-prometheus-2.8-wal-based-remote-write/) for details. The same applies also to [vmagent](https://docs.victoriametrics.com/vmagent.html).
## How to scrape Prometheus exporters such as [node-exporter](https://github.com/prometheus/node_exporter) ## How to scrape Prometheus exporters such as [node-exporter](https://github.com/prometheus/node_exporter)

View file

@ -245,7 +245,7 @@ func parseFile(path string) ([]Group, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("error reading alert rule file %q: %w", path, err) return nil, fmt.Errorf("error reading alert rule file %q: %w", path, err)
} }
data, err = envtemplate.Replace(data) data, err = envtemplate.ReplaceBytes(data)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot expand environment vars in %q: %w", path, err) return nil, fmt.Errorf("cannot expand environment vars in %q: %w", path, err)
} }

View file

@ -251,7 +251,7 @@ func readAuthConfig(path string) (map[string]*UserInfo, error) {
func parseAuthConfig(data []byte) (map[string]*UserInfo, error) { func parseAuthConfig(data []byte) (map[string]*UserInfo, error) {
var err error var err error
data, err = envtemplate.Replace(data) data, err = envtemplate.ReplaceBytes(data)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot expand environment vars: %w", err) return nil, fmt.Errorf("cannot expand environment vars: %w", err)
} }

View file

@ -1,12 +1,12 @@
{ {
"files": { "files": {
"main.css": "./static/css/main.a8f63142.css", "main.css": "./static/css/main.07bcc4ad.css",
"main.js": "./static/js/main.d67a62da.js", "main.js": "./static/js/main.3e8347de.js",
"static/js/27.939f971b.chunk.js": "./static/js/27.939f971b.chunk.js", "static/js/27.939f971b.chunk.js": "./static/js/27.939f971b.chunk.js",
"index.html": "./index.html" "index.html": "./index.html"
}, },
"entrypoints": [ "entrypoints": [
"static/css/main.a8f63142.css", "static/css/main.07bcc4ad.css",
"static/js/main.d67a62da.js" "static/js/main.3e8347de.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="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap" rel="stylesheet"><script src="./dashboards/index.js" type="module"></script><script defer="defer" src="./static/js/main.d67a62da.js"></script><link href="./static/css/main.a8f63142.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="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap" rel="stylesheet"><script src="./dashboards/index.js" type="module"></script><script defer="defer" src="./static/js/main.3e8347de.js"></script><link href="./static/css/main.07bcc4ad.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

View file

@ -0,0 +1 @@
body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:Lato,sans-serif}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}.MuiAccordionSummary-content{margin:0!important}.shortcut-key{align-items:center;border:1px solid #dedede;border-radius:4px;cursor:default;display:inline-flex;font-size:10px;justify-content:center;line-height:22px;padding:2px 6px 0;text-align:center;white-space:nowrap}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.uplot,.uplot *,.uplot :after,.uplot :before{box-sizing:border-box}.uplot{font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5;width:-webkit-min-content;width:min-content}.u-title{font-size:18px;font-weight:700;text-align:center}.u-wrap{position:relative;-webkit-user-select:none;-ms-user-select:none;user-select:none}.u-over,.u-under{position:absolute}.u-under{overflow:hidden}.uplot canvas{display:block;height:100%;position:relative;width:100%}.u-axis{position:absolute}.u-legend{font-size:14px;margin:auto;text-align:center}.u-inline{display:block}.u-inline *{display:inline-block}.u-inline tr{margin-right:16px}.u-legend th{font-weight:600}.u-legend th>*{display:inline-block;vertical-align:middle}.u-legend .u-marker{background-clip:padding-box!important;height:1em;margin-right:4px;width:1em}.u-inline.u-live th:after{content:":";vertical-align:middle}.u-inline:not(.u-live) .u-value{display:none}.u-series>*{padding:4px}.u-series th{cursor:pointer}.u-legend .u-off>*{opacity:.3}.u-select{background:rgba(0,0,0,.07)}.u-cursor-x,.u-cursor-y,.u-select{pointer-events:none;position:absolute}.u-cursor-x,.u-cursor-y{left:0;top:0;will-change:transform;z-index:100}.u-hz .u-cursor-x,.u-vt .u-cursor-y{border-right:1px dashed #607d8b;height:100%}.u-hz .u-cursor-y,.u-vt .u-cursor-x{border-bottom:1px dashed #607d8b;width:100%}.u-cursor-pt{background-clip:padding-box!important;border:0 solid;border-radius:50%;left:0;pointer-events:none;position:absolute;top:0;will-change:transform;z-index:100}.u-axis.u-off,.u-cursor-pt.u-off,.u-cursor-x.u-off,.u-cursor-y.u-off,.u-select.u-off,.u-tooltip{display:none}.u-tooltip{grid-gap:12px;word-wrap:break-word;background:rgba(57,57,57,.9);border-radius:4px;color:#fff;font-family:monospace;font-size:10px;font-weight:700;line-height:1.4em;max-width:300px;padding:8px;pointer-events:none;position:absolute;z-index:100}.u-tooltip-data{align-items:center;display:flex;flex-wrap:wrap;font-size:11px;line-height:150%}.u-tooltip-data__value{font-weight:700;padding:4px}.u-tooltip__info{grid-gap:4px;display:grid}.u-tooltip__marker{height:12px;margin-right:4px;width:12px}.legendWrapper{cursor:default;display:flex;flex-wrap:wrap;margin-top:20px;position:relative}.legendGroup{margin:0 12px 0 0;padding:10px 6px}.legendGroupTitle{align-items:center;border-bottom:1px solid #ecebe6;display:flex;font-size:11px;margin-bottom:5px;padding:0 10px 5px}.legendGroupQuery{font-weight:700;margin-right:4px}.legendGroupLine{margin-right:10px}.legendItem{grid-gap:6px;align-items:start;background-color:#fff;cursor:pointer;display:grid;grid-template-columns:auto auto;justify-content:start;padding:7px 50px 7px 10px;transition:.2s ease}.legendItemHide{opacity:.5;text-decoration:line-through}.legendItem:hover{background-color:rgba(0,0,0,.1)}.legendMarker{box-sizing:border-box;height:12px;transition:.2s ease;width:12px}.legendLabel{font-size:11px;font-weight:400;line-height:12px}.legendFreeFields{cursor:pointer;padding:3px}.legendFreeFields:hover{text-decoration:underline}.legendFreeFields:not(:last-child):after{content:","}.panelDescription ul{line-height:2.2}.panelDescription a{color:#fff}.panelDescription code{background-color:rgba(0,0,0,.3);border-radius:2px;color:#fff;display:inline;font-size:inherit;font-weight:400;max-width:100%;padding:4px 6px}

View file

@ -1 +0,0 @@
body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:Lato,sans-serif}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}.MuiAccordionSummary-content{margin:0!important}.shortcut-key{align-items:center;border:1px solid #dedede;border-radius:4px;cursor:default;display:inline-flex;font-size:10px;justify-content:center;line-height:22px;padding:2px 6px 0;text-align:center;white-space:nowrap}.uplot,.uplot *,.uplot :after,.uplot :before{box-sizing:border-box}.uplot{font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5;width:-webkit-min-content;width:min-content}.u-title{font-size:18px;font-weight:700;text-align:center}.u-wrap{position:relative;-webkit-user-select:none;-ms-user-select:none;user-select:none}.u-over,.u-under{position:absolute}.u-under{overflow:hidden}.uplot canvas{display:block;height:100%;position:relative;width:100%}.u-axis{position:absolute}.u-legend{font-size:14px;margin:auto;text-align:center}.u-inline{display:block}.u-inline *{display:inline-block}.u-inline tr{margin-right:16px}.u-legend th{font-weight:600}.u-legend th>*{display:inline-block;vertical-align:middle}.u-legend .u-marker{background-clip:padding-box!important;height:1em;margin-right:4px;width:1em}.u-inline.u-live th:after{content:":";vertical-align:middle}.u-inline:not(.u-live) .u-value{display:none}.u-series>*{padding:4px}.u-series th{cursor:pointer}.u-legend .u-off>*{opacity:.3}.u-select{background:rgba(0,0,0,.07)}.u-cursor-x,.u-cursor-y,.u-select{pointer-events:none;position:absolute}.u-cursor-x,.u-cursor-y{left:0;top:0;will-change:transform;z-index:100}.u-hz .u-cursor-x,.u-vt .u-cursor-y{border-right:1px dashed #607d8b;height:100%}.u-hz .u-cursor-y,.u-vt .u-cursor-x{border-bottom:1px dashed #607d8b;width:100%}.u-cursor-pt{background-clip:padding-box!important;border:0 solid;border-radius:50%;left:0;pointer-events:none;position:absolute;top:0;will-change:transform;z-index:100}.u-axis.u-off,.u-cursor-pt.u-off,.u-cursor-x.u-off,.u-cursor-y.u-off,.u-select.u-off,.u-tooltip{display:none}.u-tooltip{grid-gap:12px;word-wrap:break-word;background:rgba(57,57,57,.9);border-radius:4px;color:#fff;font-family:monospace;font-size:10px;font-weight:700;line-height:1.4em;max-width:300px;padding:8px;pointer-events:none;position:absolute;z-index:100}.u-tooltip-data{align-items:center;display:flex;flex-wrap:wrap;font-size:11px;line-height:150%}.u-tooltip-data__value{font-weight:700;padding:4px}.u-tooltip__info{grid-gap:4px;display:grid}.u-tooltip__marker{height:12px;margin-right:4px;width:12px}.legendWrapper{cursor:default;display:flex;flex-wrap:wrap;margin-top:20px;position:relative}.legendGroup{margin:0 12px 0 0;padding:10px 6px}.legendGroupTitle{align-items:center;border-bottom:1px solid #ecebe6;display:flex;font-size:11px;margin-bottom:5px;padding:0 10px 5px}.legendGroupQuery{font-weight:700;margin-right:4px}.legendGroupLine{margin-right:10px}.legendItem{grid-gap:6px;align-items:start;background-color:#fff;cursor:pointer;display:grid;grid-template-columns:auto auto;justify-content:start;padding:7px 50px 7px 10px;transition:.2s ease}.legendItemHide{opacity:.5;text-decoration:line-through}.legendItem:hover{background-color:rgba(0,0,0,.1)}.legendMarker{box-sizing:border-box;height:12px;transition:.2s ease;width:12px}.legendLabel{font-size:11px;font-weight:400;line-height:12px}.legendFreeFields{cursor:pointer;padding:3px}.legendFreeFields:hover{text-decoration:underline}.legendFreeFields:not(:last-child):after{content:","}.panelDescription ul{line-height:2.2}.panelDescription a{color:#fff}.panelDescription code{background-color:rgba(0,0,0,.3);border-radius:2px;color:#fff;display:inline;font-size:inherit;font-weight:400;max-width:100%;padding:4px 6px}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -32,31 +32,30 @@ const AdditionalSettings: FC = () => {
saveToStorage("QUERY_TRACING", !isTracingEnabled); saveToStorage("QUERY_TRACING", !isTracingEnabled);
}; };
return <Box display="flex" alignItems="center"> return <Box display="flex" alignItems="center" flexWrap="wrap" gap={2}>
<Box> <Box>
<FormControlLabel label="Autocomplete" <FormControlLabel label="Autocomplete" sx={{m: 0}}
control={<BasicSwitch checked={autocomplete} onChange={onChangeAutocomplete}/>} control={<BasicSwitch checked={autocomplete} onChange={onChangeAutocomplete}/>}
/> />
</Box> </Box>
<Box ml={2}> <Box>
<FormControlLabel label="Disable cache" <FormControlLabel label="Disable cache" sx={{m: 0}}
control={<BasicSwitch checked={nocache} onChange={onChangeCache}/>} control={<BasicSwitch checked={nocache} onChange={onChangeCache}/>}
/> />
</Box> </Box>
<Box ml={2}> <Box>
<FormControlLabel label="Trace query" <FormControlLabel label="Trace query" sx={{m: 0}}
control={<BasicSwitch checked={isTracingEnabled} onChange={onChangeQueryTracing} />} control={<BasicSwitch checked={isTracingEnabled} onChange={onChangeQueryTracing} />}
/> />
</Box> </Box>
<Box ml={2} mr={inputTenantID ? 0 : 2}> <Box ml={2}>
<StepConfigurator <StepConfigurator defaultStep={step}
defaultStep={step}
setStep={(value) => { setStep={(value) => {
graphDispatch({type: "SET_CUSTOM_STEP", payload: value}); graphDispatch({type: "SET_CUSTOM_STEP", payload: value});
}} }}
/> />
</Box> </Box>
{!!inputTenantID && <Box sx={{mx: 3}}><TenantsConfiguration/></Box>} {!!inputTenantID && <Box ml={2}><TenantsConfiguration/></Box>}
</Box>; </Box>;
}; };

View file

@ -91,11 +91,11 @@ const QueryConfigurator: FC<QueryConfiguratorProps> = ({error, queryOptions}) =>
</Tooltip>} </Tooltip>}
</Box>)} </Box>)}
</Box> </Box>
<Box mt={3} display="grid" gridTemplateColumns="1fr auto" alignItems="center"> <Box mt={3} display="grid" gridTemplateColumns="1fr auto" alignItems="start" gap={4}>
<AdditionalSettings/> <AdditionalSettings/>
<Box> <Box display="grid" gridTemplateColumns="repeat(2, auto)" gap={1}>
{stateQuery.length < MAX_QUERY_FIELDS && ( {stateQuery.length < MAX_QUERY_FIELDS && (
<Button variant="outlined" onClick={onAddQuery} startIcon={<AddIcon/>} sx={{mr: 2}}> <Button variant="outlined" onClick={onAddQuery} startIcon={<AddIcon/>}>
<Typography lineHeight={"20px"} fontWeight="500">Add Query</Typography> <Typography lineHeight={"20px"} fontWeight="500">Add Query</Typography>
</Button> </Button>
)} )}

View file

@ -1,7 +1,11 @@
import React, {FC, useCallback, useState} from "preact/compat"; import React, {FC, useCallback, useState} from "preact/compat";
import {ChangeEvent} from "react"; import {ChangeEvent, useEffect} from "react";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import debounce from "lodash.debounce"; import debounce from "lodash.debounce";
import InputAdornment from "@mui/material/InputAdornment";
import Tooltip from "@mui/material/Tooltip";
import RestartAltIcon from "@mui/icons-material/RestartAlt";
import IconButton from "@mui/material/IconButton";
interface StepConfiguratorProps { interface StepConfiguratorProps {
defaultStep?: number, defaultStep?: number,
@ -18,6 +22,11 @@ const StepConfigurator: FC<StepConfiguratorProps> = ({defaultStep, setStep}) =>
const onChangeStep = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { const onChangeStep = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const value = +e.target.value; const value = +e.target.value;
if (!value) return;
handleSetStep(value);
};
const handleSetStep = (value: number) => {
if (value > 0) { if (value > 0) {
setCustomStep(value); setCustomStep(value);
debouncedHandleApply(value); debouncedHandleApply(value);
@ -27,6 +36,10 @@ const StepConfigurator: FC<StepConfiguratorProps> = ({defaultStep, setStep}) =>
} }
}; };
useEffect(() => {
if (defaultStep) handleSetStep(defaultStep);
}, [defaultStep]);
return <TextField return <TextField
label="Step value" label="Step value"
type="number" type="number"
@ -35,7 +48,20 @@ const StepConfigurator: FC<StepConfiguratorProps> = ({defaultStep, setStep}) =>
value={customStep} value={customStep}
error={error} error={error}
helperText={error ? "step is out of allowed range" : " "} helperText={error ? "step is out of allowed range" : " "}
onChange={onChangeStep}/>; onChange={onChangeStep}
InputProps={{
inputProps: {min: 0},
endAdornment: (
<InputAdornment position="start" sx={{mr: -0.5, cursor: "pointer"}}>
<Tooltip title={"Reset step to default"}>
<IconButton size={"small"} onClick={() => handleSetStep(defaultStep || 1)}>
<RestartAltIcon fontSize={"small"} />
</IconButton>
</Tooltip>
</InputAdornment>
),
}}
/>;
}; };
export default StepConfigurator; export default StepConfigurator;

View file

@ -15,6 +15,7 @@ import Divider from "@mui/material/Divider";
import ClickAwayListener from "@mui/material/ClickAwayListener"; import ClickAwayListener from "@mui/material/ClickAwayListener";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
import AlarmAdd from "@mui/icons-material/AlarmAdd"; import AlarmAdd from "@mui/icons-material/AlarmAdd";
import useMediaQuery from "@mui/material/useMediaQuery";
import {getAppModeEnable} from "../../../../utils/app-mode"; import {getAppModeEnable} from "../../../../utils/app-mode";
const formatDate = "YYYY-MM-DD HH:mm:ss"; const formatDate = "YYYY-MM-DD HH:mm:ss";
@ -38,6 +39,8 @@ const classes = {
export const TimeSelector: FC = () => { export const TimeSelector: FC = () => {
const displayFullDate = useMediaQuery("(min-width: 1120px)");
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null); const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
const [until, setUntil] = useState<string>(); const [until, setUntil] = useState<string>();
const [from, setFrom] = useState<string>(); const [from, setFrom] = useState<string>();
@ -99,13 +102,17 @@ export const TimeSelector: FC = () => {
sx={{ sx={{
color: "white", color: "white",
border: appModeEnable ? "none" : "1px solid rgba(0, 0, 0, 0.2)", border: appModeEnable ? "none" : "1px solid rgba(0, 0, 0, 0.2)",
boxShadow: "none" boxShadow: "none",
minWidth: "34px",
padding: displayFullDate ? "" : "6px 8px",
}} }}
startIcon={<QueryBuilderIcon/>} startIcon={<QueryBuilderIcon style={displayFullDate ? {} : {marginRight: "-8px", marginLeft: "4px"}}/>}
onClick={(e) => setAnchorEl(e.currentTarget)}> onClick={(e) => setAnchorEl(e.currentTarget)}>
{relativeTime && relativeTime !== "none" {displayFullDate && <span>
? relativeTime.replace(/_/g, " ") {relativeTime && relativeTime !== "none"
: `${formatRange.start} - ${formatRange.end}`} ? relativeTime.replace(/_/g, " ")
: `${formatRange.start} - ${formatRange.end}`}
</span>}
</Button> </Button>
</Tooltip> </Tooltip>
<Popper <Popper

View file

@ -4,10 +4,11 @@ import LineChart from "../../LineChart/LineChart";
import {AlignedData as uPlotData, Series as uPlotSeries} from "uplot"; import {AlignedData as uPlotData, Series as uPlotSeries} from "uplot";
import Legend from "../../Legend/Legend"; import Legend from "../../Legend/Legend";
import {getHideSeries, getLegendItem, getSeriesItem} from "../../../utils/uplot/series"; import {getHideSeries, getLegendItem, getSeriesItem} from "../../../utils/uplot/series";
import {getLimitsYAxis, getTimeSeries} from "../../../utils/uplot/axes"; import {getLimitsYAxis, getMinMaxBuffer, getTimeSeries} from "../../../utils/uplot/axes";
import {LegendItem} from "../../../utils/uplot/types"; import {LegendItem} from "../../../utils/uplot/types";
import {TimeParams} from "../../../types"; import {TimeParams} from "../../../types";
import {AxisRange, YaxisState} from "../../../state/graph/reducer"; import {AxisRange, YaxisState} from "../../../state/graph/reducer";
import {getAvgFromArray, getMaxFromArray, getMinFromArray} from "../../../utils/math";
export interface GraphViewProps { export interface GraphViewProps {
data?: MetricResult[]; data?: MetricResult[];
@ -20,6 +21,7 @@ export interface GraphViewProps {
showLegend?: boolean; showLegend?: boolean;
setYaxisLimits: (val: AxisRange) => void setYaxisLimits: (val: AxisRange) => void
setPeriod: ({from, to}: {from: Date, to: Date}) => void setPeriod: ({from, to}: {from: Date, to: Date}) => void
fullWidth?: boolean
} }
const promValueToNumber = (s: string): number => { const promValueToNumber = (s: string): number => {
@ -47,7 +49,8 @@ const GraphView: FC<GraphViewProps> = ({
showLegend= true, showLegend= true,
setYaxisLimits, setYaxisLimits,
setPeriod, setPeriod,
alias = [] alias = [],
fullWidth = true
}) => { }) => {
const currentStep = useMemo(() => customStep || period.step || 1, [period.step, customStep]); const currentStep = useMemo(() => customStep || period.step || 1, [period.step, customStep]);
@ -102,10 +105,16 @@ const GraphView: FC<GraphViewProps> = ({
} }
results.push(v); results.push(v);
} }
return results;
// stabilize float numbers
const resultAsNumber = results.filter(s => s !== null) as number[];
const avg = Math.abs(getAvgFromArray(resultAsNumber));
const range = getMinMaxBuffer(getMinFromArray(resultAsNumber), getMaxFromArray(resultAsNumber));
const rangeStep = Math.abs(range[1] - range[0]);
return (avg > rangeStep * 1e10) ? results.map(() => avg) : results;
}); });
timeDataSeries.unshift(timeSeries); timeDataSeries.unshift(timeSeries);
setLimitsYaxis(tempValues); setLimitsYaxis(tempValues);
setDataChart(timeDataSeries as uPlotData); setDataChart(timeDataSeries as uPlotData);
setSeries(tempSeries); setSeries(tempSeries);
@ -127,7 +136,7 @@ const GraphView: FC<GraphViewProps> = ({
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
return <> return <>
<div style={{width: "100%"}} ref={containerRef}> <div style={{width: fullWidth ? "calc(100vw - 68px)" : "100%"}} ref={containerRef}>
{containerRef?.current && {containerRef?.current &&
<LineChart data={dataChart} series={series} metrics={data} period={period} yaxis={yaxis} unit={unit} <LineChart data={dataChart} series={series} metrics={data} period={period} yaxis={yaxis} unit={unit}
setPeriod={setPeriod} container={containerRef?.current}/>} setPeriod={setPeriod} container={containerRef?.current}/>}

View file

@ -35,6 +35,7 @@ const classes = {
color: "inherit", color: "inherit",
textDecoration: "underline", textDecoration: "underline",
transition: ".2s opacity", transition: ".2s opacity",
whiteSpace: "nowrap",
"&:hover": { "&:hover": {
opacity: ".8", opacity: ".8",
} }
@ -121,7 +122,7 @@ const Header: FC = () => {
</Link> </Link>
</Box> </Box>
)} )}
<Box sx={{ml: appModeEnable ? 0 : 8}}> <Box ml={appModeEnable ? 0 : 8} flexGrow={1}>
<Tabs value={activeMenu} textColor="inherit" TabIndicatorProps={{style: {background: color}}} <Tabs value={activeMenu} textColor="inherit" TabIndicatorProps={{style: {background: color}}}
onChange={(e, val) => setActiveMenu(val)}> onChange={(e, val) => setActiveMenu(val)}>
{routes.filter(r => !r.hide).map(r => ( {routes.filter(r => !r.hide).map(r => (
@ -136,7 +137,7 @@ const Header: FC = () => {
))} ))}
</Tabs> </Tabs>
</Box> </Box>
<Box display="flex" gap={1} alignItems="center" ml="auto" mr={0}> <Box display="flex" gap={1} alignItems="center" mr={0} ml={4}>
{headerSetup?.timeSelector && <TimeSelector/>} {headerSetup?.timeSelector && <TimeSelector/>}
{headerSetup?.datePicker && ( {headerSetup?.datePicker && (
<DatePicker <DatePicker

View file

@ -126,7 +126,9 @@ const PredefinedPanels: FC<PredefinedPanelsProps> = ({
alias={alias} alias={alias}
showLegend={showLegend} showLegend={showLegend}
setYaxisLimits={setYaxisLimits} setYaxisLimits={setYaxisLimits}
setPeriod={setPeriod}/> setPeriod={setPeriod}
fullWidth={false}
/>
} }
</Box> </Box>
</Box>; </Box>;

View file

@ -27,3 +27,9 @@ code {
border: 1px solid #dedede; border: 1px solid #dedede;
cursor: default; cursor: default;
} }
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}

View file

@ -1,3 +1,5 @@
import {getQueryStringValue} from "../../utils/query-string";
export interface AxisRange { export interface AxisRange {
[key: string]: [number, number] [key: string]: [number, number]
} }
@ -20,7 +22,7 @@ export type GraphAction =
| { type: "SET_CUSTOM_STEP", payload: number} | { type: "SET_CUSTOM_STEP", payload: number}
export const initialGraphState: GraphState = { export const initialGraphState: GraphState = {
customStep: 1, customStep: parseFloat(getQueryStringValue("g0.step_input", "0") as string),
yaxis: { yaxis: {
limits: {enable: false, range: {"1": [0, 0]}} limits: {enable: false, range: {"1": [0, 0]}}
} }

View file

@ -21,3 +21,5 @@ export const getMinFromArray = (a: number[]) => {
} }
return Number.isFinite(min) ? min : null; return Number.isFinite(min) ? min : null;
}; };
export const getAvgFromArray = (a: number[]) => a.reduce((a,b) => a+b) / a.length;

View file

@ -20,6 +20,8 @@ The following tip changes can be tested by building VictoriaMetrics components f
* FEATURE: [VictoriaMetric enterprise](https://docs.victoriametrics.com/enterprise.html): allow configuring multiple retentions for distinct sets of time series. See [these docs](https://docs.victoriametrics.com/#retention-filters), [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/143) and [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/289) feature request. * FEATURE: [VictoriaMetric enterprise](https://docs.victoriametrics.com/enterprise.html): allow configuring multiple retentions for distinct sets of time series. See [these docs](https://docs.victoriametrics.com/#retention-filters), [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/143) and [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/289) feature request.
* FEATURE: [VictoriaMetric cluster enterprise](https://docs.victoriametrics.com/enterprise.html): add support for multiple retentions for distinct tenants - see [these docs](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#retention-filters) and [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/143) and [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/289) feature request. * FEATURE: [VictoriaMetric cluster enterprise](https://docs.victoriametrics.com/enterprise.html): add support for multiple retentions for distinct tenants - see [these docs](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#retention-filters) and [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/143) and [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/289) feature request.
* FEATURE: allow limiting memory usage on a per-query basis with `-search.maxMemoryPerQuery` command-line flag. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3203). * FEATURE: allow limiting memory usage on a per-query basis with `-search.maxMemoryPerQuery` command-line flag. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3203).
* FEATURE: allow referring environment variables inside command-line flags via `%{ENV_VAR}` syntax. For example, if `AUTH_KEY=top-secret` environment variable is set, then `-metricsAuthKey=%{AUTH_KEY}` command-line flag is automatically expanded to `-storageDataPath=top-secret` at VictoriaMetrics startup. See [these docs](https://docs.victoriametrics.com/#environment-variables) for details.
* FEATURE: allow referring environment variables inside other environment variables via `%{ENV_VAR}` syntax. For example, if `A=a-%{B}`, `B=b-%{C}` and 'C=c` env vars are set, then VictoriaMetrics components automatically expand them to `A=a-b-c`, `B=b-c` and `C=c` on startup.
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): drop all the labels with `__` prefix from discovered targets in the same way as Prometheus does according to [this article](https://www.robustperception.io/life-of-a-label/). Previously the following labels were available during [metric-level relabeling](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs): `__address__`, `__scheme__`, `__metrics_path__`, `__scrape_interval__`, `__scrape_timeout__`, `__param_*`. Now these labels are available only during [target-level relabeling](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config). This should reduce CPU usage and memory usage for `vmagent` setups, which scrape big number of targets. * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): drop all the labels with `__` prefix from discovered targets in the same way as Prometheus does according to [this article](https://www.robustperception.io/life-of-a-label/). Previously the following labels were available during [metric-level relabeling](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs): `__address__`, `__scheme__`, `__metrics_path__`, `__scrape_interval__`, `__scrape_timeout__`, `__param_*`. Now these labels are available only during [target-level relabeling](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config). This should reduce CPU usage and memory usage for `vmagent` setups, which scrape big number of targets.
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): improve the performance for metric-level [relabeling](https://docs.victoriametrics.com/vmagent.html#relabeling), which can be applied via `metric_relabel_configs` section at [scrape_configs](https://docs.victoriametrics.com/sd_configs.html#scrape_configs), via `-remoteWrite.relabelConfig` or via `-remoteWrite.urlRelabelConfig` command-line options. * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): improve the performance for metric-level [relabeling](https://docs.victoriametrics.com/vmagent.html#relabeling), which can be applied via `metric_relabel_configs` section at [scrape_configs](https://docs.victoriametrics.com/sd_configs.html#scrape_configs), via `-remoteWrite.relabelConfig` or via `-remoteWrite.urlRelabelConfig` command-line options.
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): allow specifying full url in scrape target addresses (aka `__address__` label). This makes valid the following `-promscrape.config`: * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): allow specifying full url in scrape target addresses (aka `__address__` label). This makes valid the following `-promscrape.config`:
@ -44,6 +46,7 @@ The following tip changes can be tested by building VictoriaMetrics components f
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): allow controlling [staleness tracking](https://docs.victoriametrics.com/vmagent.html#prometheus-staleness-markers) on a per-[scrape_config](https://docs.victoriametrics.com/sd_configs.html#scrape_configs) basis by specifying `no_stale_markers: true` or `no_stale_markers: false` option in the corresponding [scrape_config](https://docs.victoriametrics.com/sd_configs.html#scrape_configs). * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): allow controlling [staleness tracking](https://docs.victoriametrics.com/vmagent.html#prometheus-staleness-markers) on a per-[scrape_config](https://docs.victoriametrics.com/sd_configs.html#scrape_configs) basis by specifying `no_stale_markers: true` or `no_stale_markers: false` option in the corresponding [scrape_config](https://docs.victoriametrics.com/sd_configs.html#scrape_configs).
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): limit the number of plotted series. This should prevent from browser crashes or hangs when the query returns big number of time series. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3155). * FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): limit the number of plotted series. This should prevent from browser crashes or hangs when the query returns big number of time series. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3155).
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): reduce memory usage when querying big number of time series. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3240). * FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): reduce memory usage when querying big number of time series. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3240).
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add responsive styles for small screens. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3239) and [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3256).
* FEATURE: log error if some environment variables referred at `-promscrape.config` via `%{ENV_VAR}` aren't found. This should prevent from silent using incorrect config files. * FEATURE: log error if some environment variables referred at `-promscrape.config` via `%{ENV_VAR}` aren't found. This should prevent from silent using incorrect config files.
* FEATURE: immediately shut down VictoriaMetrics apps on the second SIGINT or SIGTERM signal if they couldn't be finished gracefully for some reason after receiving the first signal. * FEATURE: immediately shut down VictoriaMetrics apps on the second SIGINT or SIGTERM signal if they couldn't be finished gracefully for some reason after receiving the first signal.
* FEATURE: improve the performance of [/api/v1/series](https://docs.victoriametrics.com/url-examples.html#apiv1series) endpoint by eliminating loading of unused `TSID` data during the API call. * FEATURE: improve the performance of [/api/v1/series](https://docs.victoriametrics.com/url-examples.html#apiv1series) endpoint by eliminating loading of unused `TSID` data during the API call.

View file

@ -184,7 +184,15 @@ It is possible manualy setting up a toy cluster on a single host. In this case e
### Environment variables ### Environment variables
Each flag values can be set through environment variables by following these rules: All the VictoriaMetrics components allow referring environment variables in command-line flags via `%{ENV_VAR}` syntax.
For example, `-metricsAuthKey=%{METRICS_AUTH_KEY}` is automatically expanded to `-metricsAuthKey=top-secret`
if `METRICS_AUTH_KEY=top-secret` environment variable exists at VictoriaMetrics startup.
This expansion is performed by VictoriaMetrics itself.
VictoriaMetrics recursively expands `%{ENV_VAR}` references in environment variables on startup.
For example, `FOO=%{BAR}` environment variable is expanded to `FOO=abc` if `BAR=a%{BAZ}` and `BAZ=bc`.
Additionally, all the VictoriaMetrics components allow setting flag values via environment variables according to these rules:
- The `-envflag.enable` flag must be set - The `-envflag.enable` flag must be set
- Each `.` in flag names must be substituted by `_` (for example `-insert.maxQueueDuration <duration>` will translate to `insert_maxQueueDuration=<duration>`) - Each `.` in flag names must be substituted by `_` (for example `-insert.maxQueueDuration <duration>` will translate to `insert_maxQueueDuration=<duration>`)

View file

@ -133,7 +133,15 @@ VictoriaMetrics is developed at a fast pace, so it is recommended periodically c
### Environment variables ### Environment variables
Each flag value can be set via environment variables according to these rules: All the VictoriaMetrics components allow referring environment variables in command-line flags via `%{ENV_VAR}` syntax.
For example, `-metricsAuthKey=%{METRICS_AUTH_KEY}` is automatically expanded to `-metricsAuthKey=top-secret`
if `METRICS_AUTH_KEY=top-secret` environment variable exists at VictoriaMetrics startup.
This expansion is performed by VictoriaMetrics itself.
VictoriaMetrics recursively expands `%{ENV_VAR}` references in environment variables on startup.
For example, `FOO=%{BAR}` environment variable is expanded to `FOO=abc` if `BAR=a%{BAZ}` and `BAZ=bc`.
Additionally, all the VictoriaMetrics components allow setting flag values via environment variables according to these rules:
* The `-envflag.enable` flag must be set. * The `-envflag.enable` flag must be set.
* Each `.` char in flag name must be substituted with `_` (for example `-insert.maxQueueDuration <duration>` will translate to `insert_maxQueueDuration=<duration>`). * Each `.` char in flag name must be substituted with `_` (for example `-insert.maxQueueDuration <duration>` will translate to `insert_maxQueueDuration=<duration>`).
@ -278,7 +286,7 @@ Multi-line queries can be entered by pressing `Shift-Enter` in query input field
When querying the [backfilled data](https://docs.victoriametrics.com/#backfilling) or during [query troubleshooting](https://docs.victoriametrics.com/Troubleshooting.html#unexpected-query-results), it may be useful disabling response cache by clicking `Disable cache` checkbox. When querying the [backfilled data](https://docs.victoriametrics.com/#backfilling) or during [query troubleshooting](https://docs.victoriametrics.com/Troubleshooting.html#unexpected-query-results), it may be useful disabling response cache by clicking `Disable cache` checkbox.
VMUI automatically adjusts the interval between datapoints on the graph depending on the horizontal resolution and on the selected time range. The step value can be customized by clickhing `Override step value` checkbox. VMUI automatically adjusts the interval between datapoints on the graph depending on the horizontal resolution and on the selected time range. The step value can be customized by changing `Step value` input.
VMUI allows investigating correlations between multiple queries on the same graph. Just click `Add Query` button, enter an additional query in the newly appeared input field and press `Ctrl+Enter`. Results for all the queries should be displayed simultaneously on the same graph. VMUI allows investigating correlations between multiple queries on the same graph. Just click `Add Query` button, enter an additional query in the newly appeared input field and press `Ctrl+Enter`. Results for all the queries should be displayed simultaneously on the same graph.
@ -322,7 +330,7 @@ VictoriaMetrics is configured via command-line flags, so it must be restarted wh
* Wait until the process stops. This can take a few seconds. * Wait until the process stops. This can take a few seconds.
* Start VictoriaMetrics with the new command-line flags. * Start VictoriaMetrics with the new command-line flags.
Prometheus doesn't drop data during VictoriaMetrics restart. See [this article](https://grafana.com/blog/2019/03/25/whats-new-in-prometheus-2.8-wal-based-remote-write/) for details. The same applies alos to [vmagent](https://docs.victoriametrics.com/vmagent.html). Prometheus doesn't drop data during VictoriaMetrics restart. See [this article](https://grafana.com/blog/2019/03/25/whats-new-in-prometheus-2.8-wal-based-remote-write/) for details. The same applies also to [vmagent](https://docs.victoriametrics.com/vmagent.html).
## How to scrape Prometheus exporters such as [node-exporter](https://github.com/prometheus/node_exporter) ## How to scrape Prometheus exporters such as [node-exporter](https://github.com/prometheus/node_exporter)

View file

@ -136,7 +136,15 @@ VictoriaMetrics is developed at a fast pace, so it is recommended periodically c
### Environment variables ### Environment variables
Each flag value can be set via environment variables according to these rules: All the VictoriaMetrics components allow referring environment variables in command-line flags via `%{ENV_VAR}` syntax.
For example, `-metricsAuthKey=%{METRICS_AUTH_KEY}` is automatically expanded to `-metricsAuthKey=top-secret`
if `METRICS_AUTH_KEY=top-secret` environment variable exists at VictoriaMetrics startup.
This expansion is performed by VictoriaMetrics itself.
VictoriaMetrics recursively expands `%{ENV_VAR}` references in environment variables on startup.
For example, `FOO=%{BAR}` environment variable is expanded to `FOO=abc` if `BAR=a%{BAZ}` and `BAZ=bc`.
Additionally, all the VictoriaMetrics components allow setting flag values via environment variables according to these rules:
* The `-envflag.enable` flag must be set. * The `-envflag.enable` flag must be set.
* Each `.` char in flag name must be substituted with `_` (for example `-insert.maxQueueDuration <duration>` will translate to `insert_maxQueueDuration=<duration>`). * Each `.` char in flag name must be substituted with `_` (for example `-insert.maxQueueDuration <duration>` will translate to `insert_maxQueueDuration=<duration>`).
@ -281,7 +289,7 @@ Multi-line queries can be entered by pressing `Shift-Enter` in query input field
When querying the [backfilled data](https://docs.victoriametrics.com/#backfilling) or during [query troubleshooting](https://docs.victoriametrics.com/Troubleshooting.html#unexpected-query-results), it may be useful disabling response cache by clicking `Disable cache` checkbox. When querying the [backfilled data](https://docs.victoriametrics.com/#backfilling) or during [query troubleshooting](https://docs.victoriametrics.com/Troubleshooting.html#unexpected-query-results), it may be useful disabling response cache by clicking `Disable cache` checkbox.
VMUI automatically adjusts the interval between datapoints on the graph depending on the horizontal resolution and on the selected time range. The step value can be customized by clickhing `Override step value` checkbox. VMUI automatically adjusts the interval between datapoints on the graph depending on the horizontal resolution and on the selected time range. The step value can be customized by changing `Step value` input.
VMUI allows investigating correlations between multiple queries on the same graph. Just click `Add Query` button, enter an additional query in the newly appeared input field and press `Ctrl+Enter`. Results for all the queries should be displayed simultaneously on the same graph. VMUI allows investigating correlations between multiple queries on the same graph. Just click `Add Query` button, enter an additional query in the newly appeared input field and press `Ctrl+Enter`. Results for all the queries should be displayed simultaneously on the same graph.
@ -325,7 +333,7 @@ VictoriaMetrics is configured via command-line flags, so it must be restarted wh
* Wait until the process stops. This can take a few seconds. * Wait until the process stops. This can take a few seconds.
* Start VictoriaMetrics with the new command-line flags. * Start VictoriaMetrics with the new command-line flags.
Prometheus doesn't drop data during VictoriaMetrics restart. See [this article](https://grafana.com/blog/2019/03/25/whats-new-in-prometheus-2.8-wal-based-remote-write/) for details. The same applies alos to [vmagent](https://docs.victoriametrics.com/vmagent.html). Prometheus doesn't drop data during VictoriaMetrics restart. See [this article](https://grafana.com/blog/2019/03/25/whats-new-in-prometheus-2.8-wal-based-remote-write/) for details. The same applies also to [vmagent](https://docs.victoriametrics.com/vmagent.html).
## How to scrape Prometheus exporters such as [node-exporter](https://github.com/prometheus/node_exporter) ## How to scrape Prometheus exporters such as [node-exporter](https://github.com/prometheus/node_exporter)

View file

@ -5,7 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"os"
"strings" "strings"
"time" "time"
@ -19,6 +18,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common" "github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/fscommon" "github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/fscommon"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/envtemplate"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger" "github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
) )
@ -59,15 +59,15 @@ func (fs *FS) Init() error {
var sc *service.Client var sc *service.Client
var err error var err error
if cs, ok := os.LookupEnv(envStorageAccCs); ok { if cs, ok := envtemplate.LookupEnv(envStorageAccCs); ok {
sc, err = service.NewClientFromConnectionString(cs, nil) sc, err = service.NewClientFromConnectionString(cs, nil)
if err != nil { if err != nil {
return fmt.Errorf("failed to create AZBlob service client from connection string: %w", err) return fmt.Errorf("failed to create AZBlob service client from connection string: %w", err)
} }
} }
accountName, ok1 := os.LookupEnv(envStorageAcctName) accountName, ok1 := envtemplate.LookupEnv(envStorageAcctName)
accountKey, ok2 := os.LookupEnv(envStorageAccKey) accountKey, ok2 := envtemplate.LookupEnv(envStorageAccKey)
if ok1 && ok2 { if ok1 && ok2 {
creds, err := azblob.NewSharedKeyCredential(accountName, accountKey) creds, err := azblob.NewSharedKeyCredential(accountName, accountKey)
if err != nil { if err != nil {

View file

@ -5,6 +5,8 @@ import (
"log" "log"
"os" "os"
"strings" "strings"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/envtemplate"
) )
var ( var (
@ -20,6 +22,22 @@ var (
// //
// This function must be called instead of flag.Parse() before using any flags in the program. // This function must be called instead of flag.Parse() before using any flags in the program.
func Parse() { func Parse() {
// Substitute %{ENV_VAR} inside args with the corresponding environment variable values
args := os.Args[1:]
dstArgs := args[:0]
for _, arg := range args {
s, err := envtemplate.ReplaceString(arg)
if err != nil {
// Do not use lib/logger here, since it is uninitialized yet.
log.Fatalf("cannot process arg %q: %s", arg, err)
}
if len(s) > 0 {
dstArgs = append(dstArgs, s)
}
}
os.Args = os.Args[:1+len(dstArgs)]
// Parse flags
flag.Parse() flag.Parse()
if !*enable { if !*enable {
return return
@ -39,7 +57,7 @@ func Parse() {
} }
// Get flag value from environment var. // Get flag value from environment var.
fname := getEnvFlagName(f.Name) fname := getEnvFlagName(f.Name)
if v, ok := os.LookupEnv(fname); ok { if v, ok := envtemplate.LookupEnv(fname); ok {
if err := flag.Set(f.Name, v); err != nil { if err := flag.Set(f.Name, v); err != nil {
// Do not use lib/logger here, since it is uninitialized yet. // Do not use lib/logger here, since it is uninitialized yet.
log.Fatalf("cannot set flag %s to %q, which is read from environment variable %q: %s", f.Name, v, fname, err) log.Fatalf("cannot set flag %s to %q, which is read from environment variable %q: %s", f.Name, v, fname, err)

View file

@ -1,31 +1,106 @@
package envtemplate package envtemplate
import ( import (
"bytes"
"fmt" "fmt"
"io" "io"
"log"
"os" "os"
"strings"
"github.com/valyala/fasttemplate" "github.com/valyala/fasttemplate"
) )
// Replace replaces `%{ENV_VAR}` placeholders in b with the corresponding ENV_VAR values. // ReplaceBytes replaces `%{ENV_VAR}` placeholders in b with the corresponding ENV_VAR values.
// //
// Error is returned if ENV_VAR isn't set for some `%{ENV_VAR}` placeholder. // Error is returned if ENV_VAR isn't set for some `%{ENV_VAR}` placeholder.
func Replace(b []byte) ([]byte, error) { func ReplaceBytes(b []byte) ([]byte, error) {
if !bytes.Contains(b, []byte("%{")) { result, err := expand(envVars, string(b))
// Fast path - nothing to replace. if err != nil {
return b, nil return nil, err
} }
s, err := fasttemplate.ExecuteFuncStringWithErr(string(b), "%{", "}", func(w io.Writer, tag string) (int, error) { return []byte(result), nil
v, ok := os.LookupEnv(tag) }
// ReplaceString replaces `%{ENV_VAR}` placeholders in b with the corresponding ENV_VAR values.
//
// Error is returned if ENV_VAR isn't set for some `%{ENV_VAR}` placeholder.
func ReplaceString(s string) (string, error) {
result, err := expand(envVars, s)
if err != nil {
return "", err
}
return result, nil
}
// LookupEnv returns the expanded environment variable value for the given name.
//
// The expanded means that `%{ENV_VAR}` placeholders in env var value are replaced
// with the corresponding ENV_VAR values (recursively).
//
// false is returned if environment variable isn't found.
func LookupEnv(name string) (string, bool) {
value, ok := envVars[name]
return value, ok
}
var envVars = func() map[string]string {
envs := os.Environ()
m := parseEnvVars(envs)
return expandTemplates(m)
}()
func parseEnvVars(envs []string) map[string]string {
m := make(map[string]string, len(envs))
for _, env := range envs {
n := strings.IndexByte(env, '=')
if n < 0 {
m[env] = ""
continue
}
name := env[:n]
value := env[n+1:]
m[name] = value
}
return m
}
func expandTemplates(m map[string]string) map[string]string {
for i := 0; i < len(m); i++ {
mExpanded := make(map[string]string, len(m))
expands := 0
for name, value := range m {
valueExpanded, err := expand(m, value)
if err != nil {
// Do not use lib/logger here, since it is uninitialized yet.
log.Fatalf("cannot expand %q env var value %q: %s", name, value, err)
}
mExpanded[name] = valueExpanded
if valueExpanded != value {
expands++
}
}
if expands == 0 {
return mExpanded
}
m = mExpanded
}
return m
}
func expand(m map[string]string, s string) (string, error) {
if !strings.Contains(s, "%{") {
// Fast path - nothing to expand
return s, nil
}
result, err := fasttemplate.ExecuteFuncStringWithErr(s, "%{", "}", func(w io.Writer, tag string) (int, error) {
v, ok := m[tag]
if !ok { if !ok {
return 0, fmt.Errorf("missing %q environment variable", tag) return 0, fmt.Errorf("missing %q env var", tag)
} }
return w.Write([]byte(v)) return w.Write([]byte(v))
}) })
if err != nil { if err != nil {
return nil, err return "", err
} }
return []byte(s), nil return result, nil
} }

View file

@ -1,22 +1,70 @@
package envtemplate package envtemplate
import ( import (
"os" "reflect"
"sort"
"testing" "testing"
) )
func TestExpandTemplates(t *testing.T) {
f := func(envs, resultExpected []string) {
t.Helper()
m := parseEnvVars(envs)
mExpanded := expandTemplates(m)
result := make([]string, 0, len(mExpanded))
for k, v := range mExpanded {
result = append(result, k+"="+v)
}
sort.Strings(result)
if !reflect.DeepEqual(result, resultExpected) {
t.Fatalf("unexpected result;\ngot\n%q\nwant\n%q", result, resultExpected)
}
}
f(nil, []string{})
f([]string{"foo=%{bar}", "bar=x"}, []string{"bar=x", "foo=x"})
f([]string{"a=x%{b}", "b=y%{c}z%{d}", "c=123", "d=qwe"}, []string{"a=xy123zqwe", "b=y123zqwe", "c=123", "d=qwe"})
f([]string{"a=x%{b}y", "b=z%{a}q", "c"}, []string{"a=xzxzxzxz%{a}qyqyqyqy", "b=zxzxzxzx%{b}yqyqyqyq", "c="})
}
func TestLookupEnv(t *testing.T) {
envVars = map[string]string{
"foo": "bar",
}
result, ok := LookupEnv("foo")
if result != "bar" {
t.Fatalf("unexpected result; got %q; want %q", result, "bar")
}
if !ok {
t.Fatalf("unexpected ok=false")
}
result, ok = LookupEnv("bar")
if result != "" {
t.Fatalf("unexpected non-empty result: %q", result)
}
if ok {
t.Fatalf("unexpected ok=true")
}
}
func TestReplaceSuccess(t *testing.T) { func TestReplaceSuccess(t *testing.T) {
if err := os.Setenv("foo", "bar"); err != nil { envVars = map[string]string{
t.Fatalf("cannot set env var: %s", err) "foo": "bar",
} }
f := func(s, resultExpected string) { f := func(s, resultExpected string) {
t.Helper() t.Helper()
result, err := Replace([]byte(s)) result, err := ReplaceBytes([]byte(s))
if err != nil { if err != nil {
t.Fatalf("unexpected error: %s", err) t.Fatalf("unexpected error: %s", err)
} }
if string(result) != resultExpected { if string(result) != resultExpected {
t.Fatalf("unexpected result;\ngot\n%q\nwant\n%q", result, resultExpected) t.Fatalf("unexpected result for ReplaceBytes(%q);\ngot\n%q\nwant\n%q", s, result, resultExpected)
}
resultS, err := ReplaceString(s)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if resultS != resultExpected {
t.Fatalf("unexpected result for ReplaceString(%q);\ngot\n%q\nwant\n%q", s, result, resultExpected)
} }
} }
f("", "") f("", "")
@ -27,9 +75,11 @@ func TestReplaceSuccess(t *testing.T) {
func TestReplaceFailure(t *testing.T) { func TestReplaceFailure(t *testing.T) {
f := func(s string) { f := func(s string) {
t.Helper() t.Helper()
_, err := Replace([]byte(s)) if _, err := ReplaceBytes([]byte(s)); err == nil {
if err == nil { t.Fatalf("expecting non-nil error for ReplaceBytes(%q)", s)
t.Fatalf("expecting non-nil error") }
if _, err := ReplaceString(s); err == nil {
t.Fatalf("expecting non-nil error for ReplaceString(%q)", s)
} }
} }
f("foo %{bar} %{baz}") f("foo %{bar} %{baz}")

View file

@ -152,7 +152,7 @@ func LoadRelabelConfigs(path string, relabelDebug bool) (*ParsedConfigs, error)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot read `relabel_configs` from %q: %w", path, err) return nil, fmt.Errorf("cannot read `relabel_configs` from %q: %w", path, err)
} }
data, err = envtemplate.Replace(data) data, err = envtemplate.ReplaceBytes(data)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot expand environment vars at %q: %w", path, err) return nil, fmt.Errorf("cannot expand environment vars at %q: %w", path, err)
} }

View file

@ -93,7 +93,7 @@ type Config struct {
func (cfg *Config) unmarshal(data []byte, isStrict bool) error { func (cfg *Config) unmarshal(data []byte, isStrict bool) error {
var err error var err error
data, err = envtemplate.Replace(data) data, err = envtemplate.ReplaceBytes(data)
if err != nil { if err != nil {
return fmt.Errorf("cannot expand environment variables: %w", err) return fmt.Errorf("cannot expand environment variables: %w", err)
} }
@ -375,7 +375,7 @@ func loadStaticConfigs(path string) ([]StaticConfig, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot read `static_configs` from %q: %w", path, err) return nil, fmt.Errorf("cannot read `static_configs` from %q: %w", path, err)
} }
data, err = envtemplate.Replace(data) data, err = envtemplate.ReplaceBytes(data)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot expand environment vars in %q: %w", path, err) return nil, fmt.Errorf("cannot expand environment vars in %q: %w", path, err)
} }
@ -419,7 +419,7 @@ func loadScrapeConfigFiles(baseDir string, scrapeConfigFiles []string) ([]*Scrap
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("cannot load %q: %w", path, err) return nil, nil, fmt.Errorf("cannot load %q: %w", path, err)
} }
data, err = envtemplate.Replace(data) data, err = envtemplate.ReplaceBytes(data)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("cannot expand environment vars in %q: %w", path, err) return nil, nil, fmt.Errorf("cannot expand environment vars in %q: %w", path, err)
} }