mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-11 14:53:49 +00:00
Merge branch 'public-single-node' into pmm-6401-read-prometheus-data-files
This commit is contained in:
commit
70bcc97d1c
30 changed files with 309 additions and 77 deletions
14
README.md
14
README.md
|
@ -132,7 +132,15 @@ VictoriaMetrics is developed at a fast pace, so it is recommended periodically c
|
|||
|
||||
### 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.
|
||||
* 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.
|
||||
|
||||
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.
|
||||
|
||||
|
@ -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.
|
||||
* 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)
|
||||
|
||||
|
|
|
@ -245,7 +245,7 @@ func parseFile(path string) ([]Group, error) {
|
|||
if err != nil {
|
||||
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 {
|
||||
return nil, fmt.Errorf("cannot expand environment vars in %q: %w", path, err)
|
||||
}
|
||||
|
|
|
@ -251,7 +251,7 @@ func readAuthConfig(path string) (map[string]*UserInfo, error) {
|
|||
|
||||
func parseAuthConfig(data []byte) (map[string]*UserInfo, error) {
|
||||
var err error
|
||||
data, err = envtemplate.Replace(data)
|
||||
data, err = envtemplate.ReplaceBytes(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot expand environment vars: %w", err)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"files": {
|
||||
"main.css": "./static/css/main.a8f63142.css",
|
||||
"main.js": "./static/js/main.d67a62da.js",
|
||||
"main.css": "./static/css/main.07bcc4ad.css",
|
||||
"main.js": "./static/js/main.3e8347de.js",
|
||||
"static/js/27.939f971b.chunk.js": "./static/js/27.939f971b.chunk.js",
|
||||
"index.html": "./index.html"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.a8f63142.css",
|
||||
"static/js/main.d67a62da.js"
|
||||
"static/css/main.07bcc4ad.css",
|
||||
"static/js/main.3e8347de.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="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>
|
1
app/vmselect/vmui/static/css/main.07bcc4ad.css
Normal file
1
app/vmselect/vmui/static/css/main.07bcc4ad.css
Normal 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}
|
|
@ -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}
|
2
app/vmselect/vmui/static/js/main.3e8347de.js
Normal file
2
app/vmselect/vmui/static/js/main.3e8347de.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -32,31 +32,30 @@ const AdditionalSettings: FC = () => {
|
|||
saveToStorage("QUERY_TRACING", !isTracingEnabled);
|
||||
};
|
||||
|
||||
return <Box display="flex" alignItems="center">
|
||||
return <Box display="flex" alignItems="center" flexWrap="wrap" gap={2}>
|
||||
<Box>
|
||||
<FormControlLabel label="Autocomplete"
|
||||
<FormControlLabel label="Autocomplete" sx={{m: 0}}
|
||||
control={<BasicSwitch checked={autocomplete} onChange={onChangeAutocomplete}/>}
|
||||
/>
|
||||
</Box>
|
||||
<Box ml={2}>
|
||||
<FormControlLabel label="Disable cache"
|
||||
<Box>
|
||||
<FormControlLabel label="Disable cache" sx={{m: 0}}
|
||||
control={<BasicSwitch checked={nocache} onChange={onChangeCache}/>}
|
||||
/>
|
||||
</Box>
|
||||
<Box ml={2}>
|
||||
<FormControlLabel label="Trace query"
|
||||
<Box>
|
||||
<FormControlLabel label="Trace query" sx={{m: 0}}
|
||||
control={<BasicSwitch checked={isTracingEnabled} onChange={onChangeQueryTracing} />}
|
||||
/>
|
||||
</Box>
|
||||
<Box ml={2} mr={inputTenantID ? 0 : 2}>
|
||||
<StepConfigurator
|
||||
defaultStep={step}
|
||||
<Box ml={2}>
|
||||
<StepConfigurator defaultStep={step}
|
||||
setStep={(value) => {
|
||||
graphDispatch({type: "SET_CUSTOM_STEP", payload: value});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{!!inputTenantID && <Box sx={{mx: 3}}><TenantsConfiguration/></Box>}
|
||||
{!!inputTenantID && <Box ml={2}><TenantsConfiguration/></Box>}
|
||||
</Box>;
|
||||
};
|
||||
|
||||
|
|
|
@ -91,11 +91,11 @@ const QueryConfigurator: FC<QueryConfiguratorProps> = ({error, queryOptions}) =>
|
|||
</Tooltip>}
|
||||
</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/>
|
||||
<Box>
|
||||
<Box display="grid" gridTemplateColumns="repeat(2, auto)" gap={1}>
|
||||
{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>
|
||||
</Button>
|
||||
)}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import React, {FC, useCallback, useState} from "preact/compat";
|
||||
import {ChangeEvent} from "react";
|
||||
import {ChangeEvent, useEffect} from "react";
|
||||
import TextField from "@mui/material/TextField";
|
||||
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 {
|
||||
defaultStep?: number,
|
||||
|
@ -18,6 +22,11 @@ const StepConfigurator: FC<StepConfiguratorProps> = ({defaultStep, setStep}) =>
|
|||
|
||||
const onChangeStep = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const value = +e.target.value;
|
||||
if (!value) return;
|
||||
handleSetStep(value);
|
||||
};
|
||||
|
||||
const handleSetStep = (value: number) => {
|
||||
if (value > 0) {
|
||||
setCustomStep(value);
|
||||
debouncedHandleApply(value);
|
||||
|
@ -27,6 +36,10 @@ const StepConfigurator: FC<StepConfiguratorProps> = ({defaultStep, setStep}) =>
|
|||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (defaultStep) handleSetStep(defaultStep);
|
||||
}, [defaultStep]);
|
||||
|
||||
return <TextField
|
||||
label="Step value"
|
||||
type="number"
|
||||
|
@ -35,7 +48,20 @@ const StepConfigurator: FC<StepConfiguratorProps> = ({defaultStep, setStep}) =>
|
|||
value={customStep}
|
||||
error={error}
|
||||
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;
|
||||
|
|
|
@ -15,6 +15,7 @@ import Divider from "@mui/material/Divider";
|
|||
import ClickAwayListener from "@mui/material/ClickAwayListener";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import AlarmAdd from "@mui/icons-material/AlarmAdd";
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
import {getAppModeEnable} from "../../../../utils/app-mode";
|
||||
|
||||
const formatDate = "YYYY-MM-DD HH:mm:ss";
|
||||
|
@ -38,6 +39,8 @@ const classes = {
|
|||
|
||||
export const TimeSelector: FC = () => {
|
||||
|
||||
const displayFullDate = useMediaQuery("(min-width: 1120px)");
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
|
||||
const [until, setUntil] = useState<string>();
|
||||
const [from, setFrom] = useState<string>();
|
||||
|
@ -99,13 +102,17 @@ export const TimeSelector: FC = () => {
|
|||
sx={{
|
||||
color: "white",
|
||||
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)}>
|
||||
{displayFullDate && <span>
|
||||
{relativeTime && relativeTime !== "none"
|
||||
? relativeTime.replace(/_/g, " ")
|
||||
: `${formatRange.start} - ${formatRange.end}`}
|
||||
</span>}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Popper
|
||||
|
|
|
@ -4,10 +4,11 @@ import LineChart from "../../LineChart/LineChart";
|
|||
import {AlignedData as uPlotData, Series as uPlotSeries} from "uplot";
|
||||
import Legend from "../../Legend/Legend";
|
||||
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 {TimeParams} from "../../../types";
|
||||
import {AxisRange, YaxisState} from "../../../state/graph/reducer";
|
||||
import {getAvgFromArray, getMaxFromArray, getMinFromArray} from "../../../utils/math";
|
||||
|
||||
export interface GraphViewProps {
|
||||
data?: MetricResult[];
|
||||
|
@ -20,6 +21,7 @@ export interface GraphViewProps {
|
|||
showLegend?: boolean;
|
||||
setYaxisLimits: (val: AxisRange) => void
|
||||
setPeriod: ({from, to}: {from: Date, to: Date}) => void
|
||||
fullWidth?: boolean
|
||||
}
|
||||
|
||||
const promValueToNumber = (s: string): number => {
|
||||
|
@ -47,7 +49,8 @@ const GraphView: FC<GraphViewProps> = ({
|
|||
showLegend= true,
|
||||
setYaxisLimits,
|
||||
setPeriod,
|
||||
alias = []
|
||||
alias = [],
|
||||
fullWidth = true
|
||||
}) => {
|
||||
const currentStep = useMemo(() => customStep || period.step || 1, [period.step, customStep]);
|
||||
|
||||
|
@ -102,10 +105,16 @@ const GraphView: FC<GraphViewProps> = ({
|
|||
}
|
||||
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);
|
||||
|
||||
setLimitsYaxis(tempValues);
|
||||
setDataChart(timeDataSeries as uPlotData);
|
||||
setSeries(tempSeries);
|
||||
|
@ -127,7 +136,7 @@ const GraphView: FC<GraphViewProps> = ({
|
|||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
return <>
|
||||
<div style={{width: "100%"}} ref={containerRef}>
|
||||
<div style={{width: fullWidth ? "calc(100vw - 68px)" : "100%"}} ref={containerRef}>
|
||||
{containerRef?.current &&
|
||||
<LineChart data={dataChart} series={series} metrics={data} period={period} yaxis={yaxis} unit={unit}
|
||||
setPeriod={setPeriod} container={containerRef?.current}/>}
|
||||
|
|
|
@ -35,6 +35,7 @@ const classes = {
|
|||
color: "inherit",
|
||||
textDecoration: "underline",
|
||||
transition: ".2s opacity",
|
||||
whiteSpace: "nowrap",
|
||||
"&:hover": {
|
||||
opacity: ".8",
|
||||
}
|
||||
|
@ -121,7 +122,7 @@ const Header: FC = () => {
|
|||
</Link>
|
||||
</Box>
|
||||
)}
|
||||
<Box sx={{ml: appModeEnable ? 0 : 8}}>
|
||||
<Box ml={appModeEnable ? 0 : 8} flexGrow={1}>
|
||||
<Tabs value={activeMenu} textColor="inherit" TabIndicatorProps={{style: {background: color}}}
|
||||
onChange={(e, val) => setActiveMenu(val)}>
|
||||
{routes.filter(r => !r.hide).map(r => (
|
||||
|
@ -136,7 +137,7 @@ const Header: FC = () => {
|
|||
))}
|
||||
</Tabs>
|
||||
</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?.datePicker && (
|
||||
<DatePicker
|
||||
|
|
|
@ -126,7 +126,9 @@ const PredefinedPanels: FC<PredefinedPanelsProps> = ({
|
|||
alias={alias}
|
||||
showLegend={showLegend}
|
||||
setYaxisLimits={setYaxisLimits}
|
||||
setPeriod={setPeriod}/>
|
||||
setPeriod={setPeriod}
|
||||
fullWidth={false}
|
||||
/>
|
||||
}
|
||||
</Box>
|
||||
</Box>;
|
||||
|
|
|
@ -27,3 +27,9 @@ code {
|
|||
border: 1px solid #dedede;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
input[type=number]::-webkit-inner-spin-button,
|
||||
input[type=number]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import {getQueryStringValue} from "../../utils/query-string";
|
||||
|
||||
export interface AxisRange {
|
||||
[key: string]: [number, number]
|
||||
}
|
||||
|
@ -20,7 +22,7 @@ export type GraphAction =
|
|||
| { type: "SET_CUSTOM_STEP", payload: number}
|
||||
|
||||
export const initialGraphState: GraphState = {
|
||||
customStep: 1,
|
||||
customStep: parseFloat(getQueryStringValue("g0.step_input", "0") as string),
|
||||
yaxis: {
|
||||
limits: {enable: false, range: {"1": [0, 0]}}
|
||||
}
|
||||
|
|
|
@ -21,3 +21,5 @@ export const getMinFromArray = (a: number[]) => {
|
|||
}
|
||||
return Number.isFinite(min) ? min : null;
|
||||
};
|
||||
|
||||
export const getAvgFromArray = (a: number[]) => a.reduce((a,b) => a+b) / a.length;
|
||||
|
|
|
@ -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 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 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): 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`:
|
||||
|
@ -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: [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): 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: 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.
|
||||
|
|
|
@ -184,7 +184,15 @@ It is possible manualy setting up a toy cluster on a single host. In this case e
|
|||
|
||||
### 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
|
||||
- Each `.` in flag names must be substituted by `_` (for example `-insert.maxQueueDuration <duration>` will translate to `insert_maxQueueDuration=<duration>`)
|
||||
|
|
|
@ -133,7 +133,15 @@ VictoriaMetrics is developed at a fast pace, so it is recommended periodically c
|
|||
|
||||
### 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.
|
||||
* 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.
|
||||
|
||||
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.
|
||||
|
||||
|
@ -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.
|
||||
* 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)
|
||||
|
||||
|
|
|
@ -136,7 +136,15 @@ VictoriaMetrics is developed at a fast pace, so it is recommended periodically c
|
|||
|
||||
### 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.
|
||||
* 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.
|
||||
|
||||
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.
|
||||
|
||||
|
@ -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.
|
||||
* 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)
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -19,6 +18,7 @@ import (
|
|||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/fscommon"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/envtemplate"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
|
@ -59,15 +59,15 @@ func (fs *FS) Init() error {
|
|||
|
||||
var sc *service.Client
|
||||
var err error
|
||||
if cs, ok := os.LookupEnv(envStorageAccCs); ok {
|
||||
if cs, ok := envtemplate.LookupEnv(envStorageAccCs); ok {
|
||||
sc, err = service.NewClientFromConnectionString(cs, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create AZBlob service client from connection string: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
accountName, ok1 := os.LookupEnv(envStorageAcctName)
|
||||
accountKey, ok2 := os.LookupEnv(envStorageAccKey)
|
||||
accountName, ok1 := envtemplate.LookupEnv(envStorageAcctName)
|
||||
accountKey, ok2 := envtemplate.LookupEnv(envStorageAccKey)
|
||||
if ok1 && ok2 {
|
||||
creds, err := azblob.NewSharedKeyCredential(accountName, accountKey)
|
||||
if err != nil {
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/envtemplate"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -20,6 +22,22 @@ var (
|
|||
//
|
||||
// This function must be called instead of flag.Parse() before using any flags in the program.
|
||||
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()
|
||||
if !*enable {
|
||||
return
|
||||
|
@ -39,7 +57,7 @@ func Parse() {
|
|||
}
|
||||
// Get flag value from environment var.
|
||||
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 {
|
||||
// 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)
|
||||
|
|
|
@ -1,31 +1,106 @@
|
|||
package envtemplate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"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.
|
||||
func Replace(b []byte) ([]byte, error) {
|
||||
if !bytes.Contains(b, []byte("%{")) {
|
||||
// Fast path - nothing to replace.
|
||||
return b, nil
|
||||
func ReplaceBytes(b []byte) ([]byte, error) {
|
||||
result, err := expand(envVars, string(b))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s, err := fasttemplate.ExecuteFuncStringWithErr(string(b), "%{", "}", func(w io.Writer, tag string) (int, error) {
|
||||
v, ok := os.LookupEnv(tag)
|
||||
return []byte(result), nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return 0, fmt.Errorf("missing %q environment variable", tag)
|
||||
return 0, fmt.Errorf("missing %q env var", tag)
|
||||
}
|
||||
return w.Write([]byte(v))
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
return []byte(s), nil
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
@ -1,22 +1,70 @@
|
|||
package envtemplate
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"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) {
|
||||
if err := os.Setenv("foo", "bar"); err != nil {
|
||||
t.Fatalf("cannot set env var: %s", err)
|
||||
envVars = map[string]string{
|
||||
"foo": "bar",
|
||||
}
|
||||
f := func(s, resultExpected string) {
|
||||
t.Helper()
|
||||
result, err := Replace([]byte(s))
|
||||
result, err := ReplaceBytes([]byte(s))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
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("", "")
|
||||
|
@ -27,9 +75,11 @@ func TestReplaceSuccess(t *testing.T) {
|
|||
func TestReplaceFailure(t *testing.T) {
|
||||
f := func(s string) {
|
||||
t.Helper()
|
||||
_, err := Replace([]byte(s))
|
||||
if err == nil {
|
||||
t.Fatalf("expecting non-nil error")
|
||||
if _, err := ReplaceBytes([]byte(s)); err == nil {
|
||||
t.Fatalf("expecting non-nil error for ReplaceBytes(%q)", s)
|
||||
}
|
||||
if _, err := ReplaceString(s); err == nil {
|
||||
t.Fatalf("expecting non-nil error for ReplaceString(%q)", s)
|
||||
}
|
||||
}
|
||||
f("foo %{bar} %{baz}")
|
||||
|
|
|
@ -152,7 +152,7 @@ func LoadRelabelConfigs(path string, relabelDebug bool) (*ParsedConfigs, error)
|
|||
if err != nil {
|
||||
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 {
|
||||
return nil, fmt.Errorf("cannot expand environment vars at %q: %w", path, err)
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ type Config struct {
|
|||
|
||||
func (cfg *Config) unmarshal(data []byte, isStrict bool) error {
|
||||
var err error
|
||||
data, err = envtemplate.Replace(data)
|
||||
data, err = envtemplate.ReplaceBytes(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot expand environment variables: %w", err)
|
||||
}
|
||||
|
@ -375,7 +375,7 @@ func loadStaticConfigs(path string) ([]StaticConfig, error) {
|
|||
if err != nil {
|
||||
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 {
|
||||
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 {
|
||||
return nil, nil, fmt.Errorf("cannot load %q: %w", path, err)
|
||||
}
|
||||
data, err = envtemplate.Replace(data)
|
||||
data, err = envtemplate.ReplaceBytes(data)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot expand environment vars in %q: %w", path, err)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue