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

This commit is contained in:
Aliaksandr Valialkin 2022-07-11 18:22:49 +03:00
commit 4a0c9a1069
No known key found for this signature in database
GPG key ID: A72BEC6CD3D0DED1
37 changed files with 1080 additions and 469 deletions

View file

@ -2,7 +2,7 @@
`vmagent` is a tiny but mighty agent which helps you collect metrics from various sources
and store them in [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)
or any other Prometheus-compatible storage systems that support the `remote_write` protocol.
or any other Prometheus-compatible storage systems with Prometheus `remote_write` protocol support.
<img alt="vmagent" src="vmagent.png">
@ -11,7 +11,8 @@ or any other Prometheus-compatible storage systems that support the `remote_writ
While VictoriaMetrics provides an efficient solution to store and observe metrics, our users needed something fast
and RAM friendly to scrape metrics from Prometheus-compatible exporters into VictoriaMetrics.
Also, we found that our user's infrastructure are like snowflakes in that no two are alike. Therefore we decided to add more flexibility
to `vmagent` such as the ability to push metrics additionally to pulling them. We did our best and will continue to improve `vmagent`.
to `vmagent` such as the ability to [accept metrics via popular push protocols](#how-to-push-data-to-vmagent)
additionally to [discovering Prometheus-compatible targets and scraping metrics from them](#how-to-collect-metrics-in-prometheus-format).
## Features
@ -89,20 +90,24 @@ There is also `-promscrape.configCheckInterval` command-line option, which can b
### IoT and Edge monitoring
`vmagent` can run and collect metrics in IoT and industrial networks with unreliable or scheduled connections to their remote storage.
`vmagent` can run and collect metrics in IoT environments and industrial networks with unreliable or scheduled connections to their remote storage.
It buffers the collected data in local files until the connection to remote storage becomes available and then sends the buffered
data to the remote storage. It re-tries sending the data to remote storage until any errors are resolved.
The maximum buffer size can be limited with `-remoteWrite.maxDiskUsagePerURL`.
data to the remote storage. It re-tries sending the data to remote storage until errors are resolved.
The maximum on-disk size for the buffered metrics can be limited with `-remoteWrite.maxDiskUsagePerURL`.
`vmagent` works on various architectures from the IoT world - 32-bit arm, 64-bit arm, ppc64, 386, amd64.
See [the corresponding Makefile rules](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmagent/Makefile) for details.
### Drop-in replacement for Prometheus
If you use Prometheus only for scraping metrics from various targets and forwarding those metrics to remote storage
If you use Prometheus only for scraping metrics from various targets and forwarding these metrics to remote storage
then `vmagent` can replace Prometheus. Typically, `vmagent` requires lower amounts of RAM, CPU and network bandwidth compared with Prometheus.
See [these docs](#how-to-collect-metrics-in-prometheus-format) for details.
### Flexible metrics relay
`vmagent` can accept metrics in [various popular data ingestion protocols](#how-to-push-data-to-vmagent), apply [relabeling](#relabeling) to the accepted metrics (for example, change metric names/labels or drop unneeded metrics) and then forward the relabeled metrics to other remote storage systems, which support Prometheus `remote_write` protocol (including other `vmagent` instances).
### Replication and high availability
`vmagent` replicates the collected metrics among multiple remote storage instances configured via `-remoteWrite.url` args.
@ -146,7 +151,7 @@ sections from [Prometheus config file](https://prometheus.io/docs/prometheus/lat
* `scrape_configs`
All other sections are ignored, including the [remote_write](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write) section.
Use `-remoteWrite.*` command-line flag instead for configuring remote write settings. See [the list of unsupported config sections](##unsupported-prometheus-config-sections).
Use `-remoteWrite.*` command-line flag instead for configuring remote write settings. See [the list of unsupported config sections](#unsupported-prometheus-config-sections).
The file pointed by `-promscrape.config` may contain `%{ENV_VAR}` placeholders which are substituted by the corresponding `ENV_VAR` environment variable values.
@ -176,6 +181,8 @@ entries to 60s. Run `vmagent -help` in order to see default values for the `-pro
Please file feature requests to [our issue tracker](https://github.com/VictoriaMetrics/VictoriaMetrics/issues) if you need other service discovery mechanisms to be supported by `vmagent`.
## scrape_config enhancements
`vmagent` supports the following additional options in `scrape_configs` section:
* `headers` - a list of HTTP headers to send to scrape target with each scrape request. This can be used when the scrape target needs custom authorization and authentication. For example:
@ -188,7 +195,7 @@ scrape_configs:
- "My-Auth: TopSecret"
```
* `disable_compression: true` for disableing response compression on a per-job basis. By default `vmagent` requests compressed responses from scrape targets for saving network bandwidth.
* `disable_compression: true` for disabling response compression on a per-job basis. By default `vmagent` requests compressed responses from scrape targets for saving network bandwidth.
* `disable_keepalive: true` for disabling [HTTP keep-alive connections](https://en.wikipedia.org/wiki/HTTP_persistent_connection) on a per-job basis. By default `vmagent` uses keep-alive connections to scrape targets for reducing overhead on connection re-establishing.
* `series_limit: N` for limiting the number of unique time series a single scrape target can expose. See [these docs](#cardinality-limiter).
* `stream_parse: true` for scraping targets in a streaming manner. This may be useful when targets export big number of metrics. See [these docs](#stream-parsing-mode).
@ -257,7 +264,7 @@ Extra labels can be added to metrics collected by `vmagent` via the following me
up == 0
```
* `scrape_duration_seconds` - this metric exposes scrape duration. This allows monitoring slow scrapes. For example, the following [MetricsQL query](https://docs.victoriametrics.com/MetricsQL.html) returns scrapes, which took more than 1.5 seconds:
* `scrape_duration_seconds` - this metric exposes scrape duration. This allows monitoring slow scrapes. For example, the following [MetricsQL query](https://docs.victoriametrics.com/MetricsQL.html) returns scrapes, which take more than 1.5 seconds to complete:
```metricsql
scrape_duration_seconds > 1.5
@ -269,7 +276,7 @@ Extra labels can be added to metrics collected by `vmagent` via the following me
scrape_duration_seconds / scrape_timeout_seconds > 0.8
```
* `scrape_samples_scraped` - this metric exposes the number of samples (aka metrics) collected per each scrape. This allows detecting targets, which expose too many metrics. For example, the following [MetricsQL query](https://docs.victoriametrics.com/MetricsQL.html) returns targets, which expose more than 10000 metrics:
* `scrape_samples_scraped` - this metric exposes the number of samples (aka metrics) parsed per each scrape. This allows detecting targets, which expose too many metrics. For example, the following [MetricsQL query](https://docs.victoriametrics.com/MetricsQL.html) returns targets, which expose more than 10000 metrics:
```metricsql
scrape_samples_scraped > 10000
@ -301,7 +308,7 @@ Extra labels can be added to metrics collected by `vmagent` via the following me
VictoriaMetrics components (including `vmagent`) support [Prometheus-compatible relabeling](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config) with [additional enhancements](#relabeling-enhancements) at various stages of data processing. The relabeling can be defined in the following places processed by `vmagent`:
* At the `scrape_config -> relabel_configs` section in `-promscrape.config` file. This relabeling is used for modifying labels in discovered targets and for dropping unneded targets. This relabeling can be debugged by passing `relabel_debug: true` option to the corresponding `scrape_config` section. In this case `vmagent` logs target labels before and after the relabeling and then drops the logged target.
* At the `scrape_config -> metric_relabel_configs` section in `-promscrape.config` file. This relabeling is used for modifying labels in scraped target metrics and for dropping unneeded metrics. This relabeling can be debugged by passing `metric_relabel_debug: true` option to the corresponding `scrape_config` section. In this case `vmagent` logs metrics before and after the relabeling and then drops the logged metrics.
* At the `scrape_config -> metric_relabel_configs` section in `-promscrape.config` file. This relabeling is used for modifying labels in scraped metrics and for dropping unneeded metrics. This relabeling can be debugged by passing `metric_relabel_debug: true` option to the corresponding `scrape_config` section. In this case `vmagent` logs metrics before and after the relabeling and then drops the logged metrics.
* At the `-remoteWrite.relabelConfig` file. This relabeling is used for modifying labels for all the collected metrics (inluding [metrics obtained via push-based protocols](#how-to-push-data-to-vmagent)) and for dropping unneeded metrics before sending them to all the configured `-remoteWrite.url` addresses. This relabeling can be debugged by passing `-remoteWrite.relabelDebug` command-line option to `vmagent`. In this case `vmagent` logs metrics before and after the relabeling and then drops all the logged metrics instead of sending them to remote storage.
* At the `-remoteWrite.urlRelabelConfig` files. This relabeling is used for modifying labels for metrics and for dropping unneeded metrics before sending them to a particular `-remoteWrite.url`. This relabeling can be debugged by passing `-remoteWrite.urlRelabelDebug` command-line options to `vmagent`. In this case `vmagent` logs metrics before and after the relabeling and then drops all the logged metrics instead of sending them to the corresponding `-remoteWrite.url`.
@ -367,6 +374,7 @@ VictoriaMetrics provides the following additional relabeling actions on top of s
```yaml
- action: drop_metrics
regex: "foo|bar"
```
* `graphite`: applies Graphite-style relabeling to metric name. See [these docs](#graphite-relabeling) for details.

View file

@ -481,7 +481,7 @@ or time series modification via [relabeling](https://docs.victoriametrics.com/vm
* `http://<vmalert-addr>` - UI;
* `http://<vmalert-addr>/api/v1/rules` - list of all loaded groups and rules;
* `http://<vmalert-addr>/api/v1/alerts` - list of all active alerts;
* `http://<vmalert-addr>/api/v1/<groupID>/<alertID>/status"` - get alert status by ID.
* `http://<vmalert-addr>/vmalert/api/v1/alert?group_id=<group_id>&alert_id=<alert_id>"` - get alert status by ID.
Used as alert source in AlertManager.
* `http://<vmalert-addr>/metrics` - application metrics.
* `http://<vmalert-addr>/-/reload` - hot configuration reload.
@ -681,7 +681,7 @@ The shortlist of configuration flags is the following:
How often to evaluate the rules (default 1m0s)
-external.alert.source string
External Alert Source allows to override the Source link for alerts sent to AlertManager for cases where you want to build a custom link to Grafana, Prometheus or any other service.
eg. 'explore?orgId=1&left=[\"now-1h\",\"now\",\"VictoriaMetrics\",{\"expr\": \"{{$expr|quotesEscape|crlfEscape|queryEscape}}\"},{\"mode\":\"Metrics\"},{\"ui\":[true,true,true,\"none\"]}]'.If empty '/api/v1/:groupID/alertID/status' is used
eg. 'explore?orgId=1&left=[\"now-1h\",\"now\",\"VictoriaMetrics\",{\"expr\": \"{{$expr|quotesEscape|crlfEscape|queryEscape}}\"},{\"mode\":\"Metrics\"},{\"ui\":[true,true,true,\"none\"]}]'.If empty '/vmalert/api/v1/alert?group_id=&alert_id=' is used
-external.label array
Optional label in the form 'Name=value' to add to all generated recording rules and alerts. Pass multiple -label flags in order to add multiple label sets.
Supports an array of values separated by comma or specified via multiple flags.

View file

@ -59,7 +59,7 @@ absolute path to all .tpl files in root.`)
externalURL = flag.String("external.url", "", "External URL is used as alert's source for sent alerts to the notifier")
externalAlertSource = flag.String("external.alert.source", "", `External Alert Source allows to override the Source link for alerts sent to AlertManager for cases where you want to build a custom link to Grafana, Prometheus or any other service.
eg. 'explore?orgId=1&left=[\"now-1h\",\"now\",\"VictoriaMetrics\",{\"expr\": \"{{$expr|quotesEscape|crlfEscape|queryEscape}}\"},{\"mode\":\"Metrics\"},{\"ui\":[true,true,true,\"none\"]}]'.If empty '/api/v1/:groupID/alertID/status' is used`)
eg. 'explore?orgId=1&left=[\"now-1h\",\"now\",\"VictoriaMetrics\",{\"expr\": \"{{$expr|quotesEscape|crlfEscape|queryEscape}}\"},{\"mode\":\"Metrics\"},{\"ui\":[true,true,true,\"none\"]}]'.If empty '/vmalert/api/v1/alert?group_id=&alert_id=' is used`)
externalLabels = flagutil.NewArray("external.label", "Optional label in the form 'Name=value' to add to all generated recording rules and alerts. "+
"Pass multiple -label flags in order to add multiple label sets.")
@ -236,8 +236,9 @@ func getExternalURL(externalURL, httpListenAddr string, isSecure bool) (*url.URL
func getAlertURLGenerator(externalURL *url.URL, externalAlertSource string, validateTemplate bool) (notifier.AlertURLGenerator, error) {
if externalAlertSource == "" {
return func(alert notifier.Alert) string {
return fmt.Sprintf("%s/api/v1/%s/%s/status", externalURL, strconv.FormatUint(alert.GroupID, 10), strconv.FormatUint(alert.ID, 10))
return func(a notifier.Alert) string {
gID, aID := strconv.FormatUint(a.GroupID, 10), strconv.FormatUint(a.ID, 10)
return fmt.Sprintf("%s/vmalert/api/v1/alert?%s=%s&%s=%s", externalURL, paramGroupID, gID, paramAlertID, aID)
}, nil
}
if validateTemplate {

View file

@ -41,7 +41,8 @@ func TestGetAlertURLGenerator(t *testing.T) {
if err != nil {
t.Errorf("unexpected error %s", err)
}
if exp := "https://victoriametrics.com/path/api/v1/42/2/status"; exp != fn(testAlert) {
exp := fmt.Sprintf("https://victoriametrics.com/path/vmalert/api/v1/alert?%s=42&%s=2", paramGroupID, paramAlertID)
if exp != fn(testAlert) {
t.Errorf("unexpected url want %s, got %s", exp, fn(testAlert))
}
_, err = getAlertURLGenerator(nil, "foo?{{invalid}}", true)

View file

@ -1,16 +1,12 @@
{% import (
"net/http"
"strings"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
) %}
{% func Footer(r *http.Request) %}
{%code
prefix := "/vmalert/"
if strings.HasPrefix(r.URL.Path, prefix) {
prefix = ""
}
%}
{%code prefix := utils.Prefix(r.URL.Path) %}
</main>
<script src="{%s prefix %}static/js/jquery-3.6.0.min.js" type="text/javascript"></script>
<script src="{%s prefix %}static/js/bootstrap.bundle.min.js" type="text/javascript"></script>

View file

@ -7,45 +7,43 @@ package tpl
//line app/vmalert/tpl/footer.qtpl:1
import (
"net/http"
"strings"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
)
//line app/vmalert/tpl/footer.qtpl:7
//line app/vmalert/tpl/footer.qtpl:8
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line app/vmalert/tpl/footer.qtpl:7
//line app/vmalert/tpl/footer.qtpl:8
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line app/vmalert/tpl/footer.qtpl:7
//line app/vmalert/tpl/footer.qtpl:8
func StreamFooter(qw422016 *qt422016.Writer, r *http.Request) {
//line app/vmalert/tpl/footer.qtpl:7
//line app/vmalert/tpl/footer.qtpl:8
qw422016.N().S(`
`)
`)
//line app/vmalert/tpl/footer.qtpl:9
prefix := "/vmalert/"
if strings.HasPrefix(r.URL.Path, prefix) {
prefix = ""
}
prefix := utils.Prefix(r.URL.Path)
//line app/vmalert/tpl/footer.qtpl:13
//line app/vmalert/tpl/footer.qtpl:9
qw422016.N().S(`
</main>
<script src="`)
//line app/vmalert/tpl/footer.qtpl:15
//line app/vmalert/tpl/footer.qtpl:11
qw422016.E().S(prefix)
//line app/vmalert/tpl/footer.qtpl:15
//line app/vmalert/tpl/footer.qtpl:11
qw422016.N().S(`static/js/jquery-3.6.0.min.js" type="text/javascript"></script>
<script src="`)
//line app/vmalert/tpl/footer.qtpl:16
//line app/vmalert/tpl/footer.qtpl:12
qw422016.E().S(prefix)
//line app/vmalert/tpl/footer.qtpl:16
//line app/vmalert/tpl/footer.qtpl:12
qw422016.N().S(`static/js/bootstrap.bundle.min.js" type="text/javascript"></script>
<script type="text/javascript">
function expandAll() {
@ -79,31 +77,31 @@ func StreamFooter(qw422016 *qt422016.Writer, r *http.Request) {
</body>
</html>
`)
//line app/vmalert/tpl/footer.qtpl:48
//line app/vmalert/tpl/footer.qtpl:44
}
//line app/vmalert/tpl/footer.qtpl:48
//line app/vmalert/tpl/footer.qtpl:44
func WriteFooter(qq422016 qtio422016.Writer, r *http.Request) {
//line app/vmalert/tpl/footer.qtpl:48
//line app/vmalert/tpl/footer.qtpl:44
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/tpl/footer.qtpl:48
//line app/vmalert/tpl/footer.qtpl:44
StreamFooter(qw422016, r)
//line app/vmalert/tpl/footer.qtpl:48
//line app/vmalert/tpl/footer.qtpl:44
qt422016.ReleaseWriter(qw422016)
//line app/vmalert/tpl/footer.qtpl:48
//line app/vmalert/tpl/footer.qtpl:44
}
//line app/vmalert/tpl/footer.qtpl:48
//line app/vmalert/tpl/footer.qtpl:44
func Footer(r *http.Request) string {
//line app/vmalert/tpl/footer.qtpl:48
//line app/vmalert/tpl/footer.qtpl:44
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/tpl/footer.qtpl:48
//line app/vmalert/tpl/footer.qtpl:44
WriteFooter(qb422016, r)
//line app/vmalert/tpl/footer.qtpl:48
//line app/vmalert/tpl/footer.qtpl:44
qs422016 := string(qb422016.B)
//line app/vmalert/tpl/footer.qtpl:48
//line app/vmalert/tpl/footer.qtpl:44
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/tpl/footer.qtpl:48
//line app/vmalert/tpl/footer.qtpl:44
return qs422016
//line app/vmalert/tpl/footer.qtpl:48
//line app/vmalert/tpl/footer.qtpl:44
}

View file

@ -2,15 +2,12 @@
"strings"
"net/http"
"path"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
) %}
{% func Header(r *http.Request, navItems []NavItem, title string) %}
{%code
prefix := "/vmalert/"
if strings.HasPrefix(r.URL.Path, prefix) {
prefix = ""
}
%}
{%code prefix := utils.Prefix(r.URL.Path) %}
<!DOCTYPE html>
<html lang="en">
<head>

View file

@ -9,52 +9,51 @@ import (
"net/http"
"path"
"strings"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
)
//line app/vmalert/tpl/header.qtpl:7
//line app/vmalert/tpl/header.qtpl:9
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line app/vmalert/tpl/header.qtpl:7
//line app/vmalert/tpl/header.qtpl:9
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line app/vmalert/tpl/header.qtpl:7
func StreamHeader(qw422016 *qt422016.Writer, r *http.Request, navItems []NavItem, title string) {
//line app/vmalert/tpl/header.qtpl:7
qw422016.N().S(`
`)
//line app/vmalert/tpl/header.qtpl:9
prefix := "/vmalert/"
if strings.HasPrefix(r.URL.Path, prefix) {
prefix = ""
}
func StreamHeader(qw422016 *qt422016.Writer, r *http.Request, navItems []NavItem, title string) {
//line app/vmalert/tpl/header.qtpl:9
qw422016.N().S(`
`)
//line app/vmalert/tpl/header.qtpl:10
prefix := utils.Prefix(r.URL.Path)
//line app/vmalert/tpl/header.qtpl:13
//line app/vmalert/tpl/header.qtpl:10
qw422016.N().S(`
<!DOCTYPE html>
<html lang="en">
<head>
<title>vmalert`)
//line app/vmalert/tpl/header.qtpl:17
//line app/vmalert/tpl/header.qtpl:14
if title != "" {
//line app/vmalert/tpl/header.qtpl:17
//line app/vmalert/tpl/header.qtpl:14
qw422016.N().S(` - `)
//line app/vmalert/tpl/header.qtpl:17
//line app/vmalert/tpl/header.qtpl:14
qw422016.E().S(title)
//line app/vmalert/tpl/header.qtpl:17
//line app/vmalert/tpl/header.qtpl:14
}
//line app/vmalert/tpl/header.qtpl:17
//line app/vmalert/tpl/header.qtpl:14
qw422016.N().S(`</title>
<link href="`)
//line app/vmalert/tpl/header.qtpl:18
//line app/vmalert/tpl/header.qtpl:15
qw422016.E().S(prefix)
//line app/vmalert/tpl/header.qtpl:18
//line app/vmalert/tpl/header.qtpl:15
qw422016.N().S(`static/css/bootstrap.min.css" rel="stylesheet" />
<style>
body{
@ -105,124 +104,124 @@ func StreamHeader(qw422016 *qt422016.Writer, r *http.Request, navItems []NavItem
</head>
<body>
`)
//line app/vmalert/tpl/header.qtpl:67
//line app/vmalert/tpl/header.qtpl:64
streamprintNavItems(qw422016, r, title, navItems)
//line app/vmalert/tpl/header.qtpl:67
//line app/vmalert/tpl/header.qtpl:64
qw422016.N().S(`
<main class="px-2">
`)
//line app/vmalert/tpl/header.qtpl:69
//line app/vmalert/tpl/header.qtpl:66
}
//line app/vmalert/tpl/header.qtpl:69
//line app/vmalert/tpl/header.qtpl:66
func WriteHeader(qq422016 qtio422016.Writer, r *http.Request, navItems []NavItem, title string) {
//line app/vmalert/tpl/header.qtpl:69
//line app/vmalert/tpl/header.qtpl:66
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/tpl/header.qtpl:69
//line app/vmalert/tpl/header.qtpl:66
StreamHeader(qw422016, r, navItems, title)
//line app/vmalert/tpl/header.qtpl:69
//line app/vmalert/tpl/header.qtpl:66
qt422016.ReleaseWriter(qw422016)
//line app/vmalert/tpl/header.qtpl:69
//line app/vmalert/tpl/header.qtpl:66
}
//line app/vmalert/tpl/header.qtpl:69
//line app/vmalert/tpl/header.qtpl:66
func Header(r *http.Request, navItems []NavItem, title string) string {
//line app/vmalert/tpl/header.qtpl:69
//line app/vmalert/tpl/header.qtpl:66
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/tpl/header.qtpl:69
//line app/vmalert/tpl/header.qtpl:66
WriteHeader(qb422016, r, navItems, title)
//line app/vmalert/tpl/header.qtpl:69
//line app/vmalert/tpl/header.qtpl:66
qs422016 := string(qb422016.B)
//line app/vmalert/tpl/header.qtpl:69
//line app/vmalert/tpl/header.qtpl:66
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/tpl/header.qtpl:69
//line app/vmalert/tpl/header.qtpl:66
return qs422016
//line app/vmalert/tpl/header.qtpl:69
//line app/vmalert/tpl/header.qtpl:66
}
//line app/vmalert/tpl/header.qtpl:73
//line app/vmalert/tpl/header.qtpl:70
type NavItem struct {
Name string
Url string
}
//line app/vmalert/tpl/header.qtpl:79
//line app/vmalert/tpl/header.qtpl:76
func streamprintNavItems(qw422016 *qt422016.Writer, r *http.Request, current string, items []NavItem) {
//line app/vmalert/tpl/header.qtpl:79
//line app/vmalert/tpl/header.qtpl:76
qw422016.N().S(`
`)
//line app/vmalert/tpl/header.qtpl:81
//line app/vmalert/tpl/header.qtpl:78
prefix := "/vmalert/"
if strings.HasPrefix(r.URL.Path, prefix) {
prefix = ""
}
//line app/vmalert/tpl/header.qtpl:85
//line app/vmalert/tpl/header.qtpl:82
qw422016.N().S(`
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<div class="container-fluid">
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav me-auto mb-2 mb-md-0">
`)
//line app/vmalert/tpl/header.qtpl:90
//line app/vmalert/tpl/header.qtpl:87
for _, item := range items {
//line app/vmalert/tpl/header.qtpl:90
//line app/vmalert/tpl/header.qtpl:87
qw422016.N().S(`
<li class="nav-item">
<a class="nav-link`)
//line app/vmalert/tpl/header.qtpl:92
//line app/vmalert/tpl/header.qtpl:89
if current == item.Name {
//line app/vmalert/tpl/header.qtpl:92
//line app/vmalert/tpl/header.qtpl:89
qw422016.N().S(` active`)
//line app/vmalert/tpl/header.qtpl:92
//line app/vmalert/tpl/header.qtpl:89
}
//line app/vmalert/tpl/header.qtpl:92
//line app/vmalert/tpl/header.qtpl:89
qw422016.N().S(`" href="`)
//line app/vmalert/tpl/header.qtpl:92
//line app/vmalert/tpl/header.qtpl:89
qw422016.E().S(path.Join(prefix, item.Url))
//line app/vmalert/tpl/header.qtpl:92
//line app/vmalert/tpl/header.qtpl:89
qw422016.N().S(`">
`)
//line app/vmalert/tpl/header.qtpl:93
//line app/vmalert/tpl/header.qtpl:90
qw422016.E().S(item.Name)
//line app/vmalert/tpl/header.qtpl:93
//line app/vmalert/tpl/header.qtpl:90
qw422016.N().S(`
</a>
</li>
`)
//line app/vmalert/tpl/header.qtpl:96
//line app/vmalert/tpl/header.qtpl:93
}
//line app/vmalert/tpl/header.qtpl:96
//line app/vmalert/tpl/header.qtpl:93
qw422016.N().S(`
</ul>
</div>
</nav>
`)
//line app/vmalert/tpl/header.qtpl:100
//line app/vmalert/tpl/header.qtpl:97
}
//line app/vmalert/tpl/header.qtpl:100
//line app/vmalert/tpl/header.qtpl:97
func writeprintNavItems(qq422016 qtio422016.Writer, r *http.Request, current string, items []NavItem) {
//line app/vmalert/tpl/header.qtpl:100
//line app/vmalert/tpl/header.qtpl:97
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/tpl/header.qtpl:100
//line app/vmalert/tpl/header.qtpl:97
streamprintNavItems(qw422016, r, current, items)
//line app/vmalert/tpl/header.qtpl:100
//line app/vmalert/tpl/header.qtpl:97
qt422016.ReleaseWriter(qw422016)
//line app/vmalert/tpl/header.qtpl:100
//line app/vmalert/tpl/header.qtpl:97
}
//line app/vmalert/tpl/header.qtpl:100
//line app/vmalert/tpl/header.qtpl:97
func printNavItems(r *http.Request, current string, items []NavItem) string {
//line app/vmalert/tpl/header.qtpl:100
//line app/vmalert/tpl/header.qtpl:97
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/tpl/header.qtpl:100
//line app/vmalert/tpl/header.qtpl:97
writeprintNavItems(qb422016, r, current, items)
//line app/vmalert/tpl/header.qtpl:100
//line app/vmalert/tpl/header.qtpl:97
qs422016 := string(qb422016.B)
//line app/vmalert/tpl/header.qtpl:100
//line app/vmalert/tpl/header.qtpl:97
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/tpl/header.qtpl:100
//line app/vmalert/tpl/header.qtpl:97
return qs422016
//line app/vmalert/tpl/header.qtpl:100
//line app/vmalert/tpl/header.qtpl:97
}

View file

@ -0,0 +1,13 @@
package utils
import "strings"
const prefix = "/vmalert/"
// Prefix returns "/vmalert/" prefix if it is missing in the path.
func Prefix(path string) string {
if strings.HasPrefix(path, prefix) {
return ""
}
return prefix
}

View file

@ -25,11 +25,11 @@ var (
func initLinks() {
apiLinks = [][2]string{
// api links are relative since they can be used by external clients
// such as Grafana and proxied via vmselect.
// api links are relative since they can be used by external clients,
// such as Grafana, and proxied via vmselect.
{"api/v1/rules", "list all loaded groups and rules"},
{"api/v1/alerts", "list all active alerts"},
{"api/v1/groupID/alertID/status", "get alert status by ID"},
{fmt.Sprintf("api/v1/alert?%s=<int>&%s=<int>", paramGroupID, paramAlertID), "get alert status by group and alert ID"},
// system links
{"/flags", "command-line flags"},
@ -76,6 +76,14 @@ func (rh *requestHandler) handler(w http.ResponseWriter, r *http.Request) bool {
case "/vmalert/alerts":
WriteListAlerts(w, r, rh.groupAlerts())
return true
case "/vmalert/alert":
alert, err := rh.getAlert(r)
if err != nil {
httpserver.Errorf(w, r, "%s", err)
return true
}
WriteAlert(w, r, alert)
return true
case "/vmalert/groups":
WriteListGroups(w, r, rh.groups())
return true
@ -111,7 +119,20 @@ func (rh *requestHandler) handler(w http.ResponseWriter, r *http.Request) bool {
w.Header().Set("Content-Type", "application/json")
w.Write(data)
return true
case "/vmalert/api/v1/alert", "/api/v1/alert":
alert, err := rh.getAlert(r)
if err != nil {
httpserver.Errorf(w, r, "%s", err)
return true
}
data, err := json.Marshal(alert)
if err != nil {
httpserver.Errorf(w, r, "failed to marshal alert: %s", err)
return true
}
w.Header().Set("Content-Type", "application/json")
w.Write(data)
return true
case "/-/reload":
logger.Infof("api config reload was called, sending sighup")
procutil.SelfSIGHUP()
@ -119,6 +140,11 @@ func (rh *requestHandler) handler(w http.ResponseWriter, r *http.Request) bool {
return true
default:
// Support of deprecated links:
// * /api/v1/<groupID>/<alertID>/status
// * <groupID>/<alertID>/status
// TODO: to remove in next versions
if !strings.HasSuffix(r.URL.Path, "/status") {
return false
}
@ -128,24 +154,36 @@ func (rh *requestHandler) handler(w http.ResponseWriter, r *http.Request) bool {
return true
}
// /api/v1/<groupID>/<alertID>/status
redirectURL := alert.WebLink()
if strings.HasPrefix(r.URL.Path, "/api/v1/") {
data, err := json.Marshal(alert)
if err != nil {
httpserver.Errorf(w, r, "failed to marshal alert: %s", err)
return true
}
w.Header().Set("Content-Type", "application/json")
w.Write(data)
return true
redirectURL = alert.APILink()
}
// <groupID>/<alertID>/status
WriteAlert(w, r, alert)
http.Redirect(w, r, "/"+redirectURL, http.StatusPermanentRedirect)
return true
}
}
const (
paramGroupID = "group_id"
paramAlertID = "alert_id"
)
func (rh *requestHandler) getAlert(r *http.Request) (*APIAlert, error) {
groupID, err := strconv.ParseUint(r.FormValue(paramGroupID), 10, 0)
if err != nil {
return nil, fmt.Errorf("failed to read %q param: %s", paramGroupID, err)
}
alertID, err := strconv.ParseUint(r.FormValue(paramAlertID), 10, 0)
if err != nil {
return nil, fmt.Errorf("failed to read %q param: %s", paramAlertID, err)
}
a, err := rh.m.AlertAPI(groupID, alertID)
if err != nil {
return nil, errResponse(err, http.StatusNotFound)
}
return a, nil
}
type listGroupsResponse struct {
Status string `json:"status"`
Data struct {
@ -245,10 +283,10 @@ func (rh *requestHandler) listAlerts() ([]byte, error) {
}
func (rh *requestHandler) alertByPath(path string) (*APIAlert, error) {
rh.m.groupsMu.RLock()
defer rh.m.groupsMu.RUnlock()
parts := strings.SplitN(strings.TrimLeft(path, "/"), "/", 3)
if strings.HasPrefix(path, "/vmalert") {
path = strings.TrimLeft(path, "/vmalert")
}
parts := strings.SplitN(strings.TrimLeft(path, "/"), "/", -1)
if len(parts) != 3 {
return nil, &httpserver.ErrorWithStatusCode{
Err: fmt.Errorf(`path %q cointains /status suffix but doesn't match pattern "/groupID/alertID/status"`, path),

View file

@ -3,10 +3,10 @@
{% import (
"time"
"sort"
"path"
"net/http"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/tpl"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
) %}
@ -119,6 +119,7 @@
{% func ListAlerts(r *http.Request, groupAlerts []GroupAlerts) %}
{%code prefix := utils.Prefix(r.URL.Path) %}
{%= tpl.Header(r, navItems, "Alerts") %}
{% if len(groupAlerts) > 0 %}
<a class="btn btn-primary" role="button" onclick="collapseAll()">Collapse All</a>
@ -183,7 +184,7 @@
</td>
<td>{%s ar.Value %}</td>
<td>
<a href="{%s path.Join(g.ID, ar.ID, "status") %}">Details</a>
<a href="{%s prefix+ar.WebLink() %}">Details</a>
</td>
</tr>
{% endfor %}
@ -261,6 +262,7 @@
{% endfunc %}
{% func Alert(r *http.Request, alert *APIAlert) %}
{%code prefix := utils.Prefix(r.URL.Path) %}
{%= tpl.Header(r, navItems, "") %}
{%code
var labelKeys []string
@ -327,7 +329,7 @@
Group
</div>
<div class="col">
<a target="_blank" href="/groups#group-{%s alert.GroupID %}">{%s alert.GroupID %}</a>
<a target="_blank" href="{%s prefix %}groups#group-{%s alert.GroupID %}">{%s alert.GroupID %}</a>
</div>
</div>
</div>

View file

@ -7,12 +7,12 @@ package main
//line app/vmalert/web.qtpl:3
import (
"net/http"
"path"
"sort"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/tpl"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
)
//line app/vmalert/web.qtpl:14
@ -434,70 +434,76 @@ func StreamListAlerts(qw422016 *qt422016.Writer, r *http.Request, groupAlerts []
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:122
tpl.StreamHeader(qw422016, r, navItems, "Alerts")
prefix := utils.Prefix(r.URL.Path)
//line app/vmalert/web.qtpl:122
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:123
if len(groupAlerts) > 0 {
tpl.StreamHeader(qw422016, r, navItems, "Alerts")
//line app/vmalert/web.qtpl:123
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:124
if len(groupAlerts) > 0 {
//line app/vmalert/web.qtpl:124
qw422016.N().S(`
<a class="btn btn-primary" role="button" onclick="collapseAll()">Collapse All</a>
<a class="btn btn-primary" role="button" onclick="expandAll()">Expand All</a>
`)
//line app/vmalert/web.qtpl:126
//line app/vmalert/web.qtpl:127
for _, ga := range groupAlerts {
//line app/vmalert/web.qtpl:126
//line app/vmalert/web.qtpl:127
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:127
//line app/vmalert/web.qtpl:128
g := ga.Group
//line app/vmalert/web.qtpl:127
//line app/vmalert/web.qtpl:128
qw422016.N().S(`
<div class="group-heading alert-danger" data-bs-target="rules-`)
//line app/vmalert/web.qtpl:128
//line app/vmalert/web.qtpl:129
qw422016.E().S(g.ID)
//line app/vmalert/web.qtpl:128
//line app/vmalert/web.qtpl:129
qw422016.N().S(`">
<span class="anchor" id="group-`)
//line app/vmalert/web.qtpl:129
//line app/vmalert/web.qtpl:130
qw422016.E().S(g.ID)
//line app/vmalert/web.qtpl:129
//line app/vmalert/web.qtpl:130
qw422016.N().S(`"></span>
<a href="#group-`)
//line app/vmalert/web.qtpl:130
//line app/vmalert/web.qtpl:131
qw422016.E().S(g.ID)
//line app/vmalert/web.qtpl:130
//line app/vmalert/web.qtpl:131
qw422016.N().S(`">`)
//line app/vmalert/web.qtpl:130
//line app/vmalert/web.qtpl:131
qw422016.E().S(g.Name)
//line app/vmalert/web.qtpl:130
//line app/vmalert/web.qtpl:131
if g.Type != "prometheus" {
//line app/vmalert/web.qtpl:130
//line app/vmalert/web.qtpl:131
qw422016.N().S(` (`)
//line app/vmalert/web.qtpl:130
//line app/vmalert/web.qtpl:131
qw422016.E().S(g.Type)
//line app/vmalert/web.qtpl:130
//line app/vmalert/web.qtpl:131
qw422016.N().S(`)`)
//line app/vmalert/web.qtpl:130
//line app/vmalert/web.qtpl:131
}
//line app/vmalert/web.qtpl:130
//line app/vmalert/web.qtpl:131
qw422016.N().S(`</a>
<span class="badge bg-danger" title="Number of active alerts">`)
//line app/vmalert/web.qtpl:131
//line app/vmalert/web.qtpl:132
qw422016.N().D(len(ga.Alerts))
//line app/vmalert/web.qtpl:131
//line app/vmalert/web.qtpl:132
qw422016.N().S(`</span>
<br>
<p class="fs-6 fw-lighter">`)
//line app/vmalert/web.qtpl:133
//line app/vmalert/web.qtpl:134
qw422016.E().S(g.File)
//line app/vmalert/web.qtpl:133
//line app/vmalert/web.qtpl:134
qw422016.N().S(`</p>
</div>
`)
//line app/vmalert/web.qtpl:136
//line app/vmalert/web.qtpl:137
var keys []string
alertsByRule := make(map[string][]*APIAlert)
for _, alert := range ga.Alerts {
@ -508,20 +514,20 @@ func StreamListAlerts(qw422016 *qt422016.Writer, r *http.Request, groupAlerts []
}
sort.Strings(keys)
//line app/vmalert/web.qtpl:145
//line app/vmalert/web.qtpl:146
qw422016.N().S(`
<div class="collapse" id="rules-`)
//line app/vmalert/web.qtpl:146
//line app/vmalert/web.qtpl:147
qw422016.E().S(g.ID)
//line app/vmalert/web.qtpl:146
//line app/vmalert/web.qtpl:147
qw422016.N().S(`">
`)
//line app/vmalert/web.qtpl:147
//line app/vmalert/web.qtpl:148
for _, ruleID := range keys {
//line app/vmalert/web.qtpl:147
//line app/vmalert/web.qtpl:148
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:149
//line app/vmalert/web.qtpl:150
defaultAR := alertsByRule[ruleID][0]
var labelKeys []string
for k := range defaultAR.Labels {
@ -529,28 +535,28 @@ func StreamListAlerts(qw422016 *qt422016.Writer, r *http.Request, groupAlerts []
}
sort.Strings(labelKeys)
//line app/vmalert/web.qtpl:155
//line app/vmalert/web.qtpl:156
qw422016.N().S(`
<br>
<b>alert:</b> `)
//line app/vmalert/web.qtpl:157
//line app/vmalert/web.qtpl:158
qw422016.E().S(defaultAR.Name)
//line app/vmalert/web.qtpl:157
//line app/vmalert/web.qtpl:158
qw422016.N().S(` (`)
//line app/vmalert/web.qtpl:157
//line app/vmalert/web.qtpl:158
qw422016.N().D(len(alertsByRule[ruleID]))
//line app/vmalert/web.qtpl:157
//line app/vmalert/web.qtpl:158
qw422016.N().S(`)
| <span><a target="_blank" href="`)
//line app/vmalert/web.qtpl:158
//line app/vmalert/web.qtpl:159
qw422016.E().S(defaultAR.SourceLink)
//line app/vmalert/web.qtpl:158
//line app/vmalert/web.qtpl:159
qw422016.N().S(`">Source</a></span>
<br>
<b>expr:</b><code><pre>`)
//line app/vmalert/web.qtpl:160
//line app/vmalert/web.qtpl:161
qw422016.E().S(defaultAR.Expression)
//line app/vmalert/web.qtpl:160
//line app/vmalert/web.qtpl:161
qw422016.N().S(`</pre></code>
<table class="table table-striped table-hover table-sm">
<thead>
@ -564,204 +570,204 @@ func StreamListAlerts(qw422016 *qt422016.Writer, r *http.Request, groupAlerts []
</thead>
<tbody>
`)
//line app/vmalert/web.qtpl:172
//line app/vmalert/web.qtpl:173
for _, ar := range alertsByRule[ruleID] {
//line app/vmalert/web.qtpl:172
//line app/vmalert/web.qtpl:173
qw422016.N().S(`
<tr>
<td>
`)
//line app/vmalert/web.qtpl:175
//line app/vmalert/web.qtpl:176
for _, k := range labelKeys {
//line app/vmalert/web.qtpl:175
//line app/vmalert/web.qtpl:176
qw422016.N().S(`
<span class="ms-1 badge bg-primary">`)
//line app/vmalert/web.qtpl:176
//line app/vmalert/web.qtpl:177
qw422016.E().S(k)
//line app/vmalert/web.qtpl:176
//line app/vmalert/web.qtpl:177
qw422016.N().S(`=`)
//line app/vmalert/web.qtpl:176
//line app/vmalert/web.qtpl:177
qw422016.E().S(ar.Labels[k])
//line app/vmalert/web.qtpl:176
//line app/vmalert/web.qtpl:177
qw422016.N().S(`</span>
`)
//line app/vmalert/web.qtpl:177
//line app/vmalert/web.qtpl:178
}
//line app/vmalert/web.qtpl:177
//line app/vmalert/web.qtpl:178
qw422016.N().S(`
</td>
<td>`)
//line app/vmalert/web.qtpl:179
//line app/vmalert/web.qtpl:180
streambadgeState(qw422016, ar.State)
//line app/vmalert/web.qtpl:179
//line app/vmalert/web.qtpl:180
qw422016.N().S(`</td>
<td>
`)
//line app/vmalert/web.qtpl:181
//line app/vmalert/web.qtpl:182
qw422016.E().S(ar.ActiveAt.Format("2006-01-02T15:04:05Z07:00"))
//line app/vmalert/web.qtpl:181
//line app/vmalert/web.qtpl:182
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:182
//line app/vmalert/web.qtpl:183
if ar.Restored {
//line app/vmalert/web.qtpl:182
//line app/vmalert/web.qtpl:183
streambadgeRestored(qw422016)
//line app/vmalert/web.qtpl:182
//line app/vmalert/web.qtpl:183
}
//line app/vmalert/web.qtpl:182
//line app/vmalert/web.qtpl:183
qw422016.N().S(`
</td>
<td>`)
//line app/vmalert/web.qtpl:184
//line app/vmalert/web.qtpl:185
qw422016.E().S(ar.Value)
//line app/vmalert/web.qtpl:184
//line app/vmalert/web.qtpl:185
qw422016.N().S(`</td>
<td>
<a href="`)
//line app/vmalert/web.qtpl:186
qw422016.E().S(path.Join(g.ID, ar.ID, "status"))
//line app/vmalert/web.qtpl:186
//line app/vmalert/web.qtpl:187
qw422016.E().S(prefix + ar.WebLink())
//line app/vmalert/web.qtpl:187
qw422016.N().S(`">Details</a>
</td>
</tr>
`)
//line app/vmalert/web.qtpl:189
//line app/vmalert/web.qtpl:190
}
//line app/vmalert/web.qtpl:189
//line app/vmalert/web.qtpl:190
qw422016.N().S(`
</tbody>
</table>
`)
//line app/vmalert/web.qtpl:192
//line app/vmalert/web.qtpl:193
}
//line app/vmalert/web.qtpl:192
//line app/vmalert/web.qtpl:193
qw422016.N().S(`
</div>
<br>
`)
//line app/vmalert/web.qtpl:195
//line app/vmalert/web.qtpl:196
}
//line app/vmalert/web.qtpl:195
//line app/vmalert/web.qtpl:196
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:197
//line app/vmalert/web.qtpl:198
} else {
//line app/vmalert/web.qtpl:197
//line app/vmalert/web.qtpl:198
qw422016.N().S(`
<div>
<p>No items...</p>
</div>
`)
//line app/vmalert/web.qtpl:201
//line app/vmalert/web.qtpl:202
}
//line app/vmalert/web.qtpl:201
//line app/vmalert/web.qtpl:202
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:203
//line app/vmalert/web.qtpl:204
tpl.StreamFooter(qw422016, r)
//line app/vmalert/web.qtpl:203
//line app/vmalert/web.qtpl:204
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:205
//line app/vmalert/web.qtpl:206
}
//line app/vmalert/web.qtpl:205
//line app/vmalert/web.qtpl:206
func WriteListAlerts(qq422016 qtio422016.Writer, r *http.Request, groupAlerts []GroupAlerts) {
//line app/vmalert/web.qtpl:205
//line app/vmalert/web.qtpl:206
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/web.qtpl:205
//line app/vmalert/web.qtpl:206
StreamListAlerts(qw422016, r, groupAlerts)
//line app/vmalert/web.qtpl:205
//line app/vmalert/web.qtpl:206
qt422016.ReleaseWriter(qw422016)
//line app/vmalert/web.qtpl:205
//line app/vmalert/web.qtpl:206
}
//line app/vmalert/web.qtpl:205
//line app/vmalert/web.qtpl:206
func ListAlerts(r *http.Request, groupAlerts []GroupAlerts) string {
//line app/vmalert/web.qtpl:205
//line app/vmalert/web.qtpl:206
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/web.qtpl:205
//line app/vmalert/web.qtpl:206
WriteListAlerts(qb422016, r, groupAlerts)
//line app/vmalert/web.qtpl:205
//line app/vmalert/web.qtpl:206
qs422016 := string(qb422016.B)
//line app/vmalert/web.qtpl:205
//line app/vmalert/web.qtpl:206
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/web.qtpl:205
//line app/vmalert/web.qtpl:206
return qs422016
//line app/vmalert/web.qtpl:205
//line app/vmalert/web.qtpl:206
}
//line app/vmalert/web.qtpl:207
//line app/vmalert/web.qtpl:208
func StreamListTargets(qw422016 *qt422016.Writer, r *http.Request, targets map[notifier.TargetType][]notifier.Target) {
//line app/vmalert/web.qtpl:207
//line app/vmalert/web.qtpl:208
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:208
//line app/vmalert/web.qtpl:209
tpl.StreamHeader(qw422016, r, navItems, "Notifiers")
//line app/vmalert/web.qtpl:208
//line app/vmalert/web.qtpl:209
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:209
//line app/vmalert/web.qtpl:210
if len(targets) > 0 {
//line app/vmalert/web.qtpl:209
//line app/vmalert/web.qtpl:210
qw422016.N().S(`
<a class="btn btn-primary" role="button" onclick="collapseAll()">Collapse All</a>
<a class="btn btn-primary" role="button" onclick="expandAll()">Expand All</a>
`)
//line app/vmalert/web.qtpl:214
//line app/vmalert/web.qtpl:215
var keys []string
for key := range targets {
keys = append(keys, string(key))
}
sort.Strings(keys)
//line app/vmalert/web.qtpl:219
//line app/vmalert/web.qtpl:220
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:221
//line app/vmalert/web.qtpl:222
for i := range keys {
//line app/vmalert/web.qtpl:221
//line app/vmalert/web.qtpl:222
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:222
//line app/vmalert/web.qtpl:223
typeK, ns := keys[i], targets[notifier.TargetType(keys[i])]
count := len(ns)
//line app/vmalert/web.qtpl:224
//line app/vmalert/web.qtpl:225
qw422016.N().S(`
<div class="group-heading data-bs-target="rules-`)
//line app/vmalert/web.qtpl:225
//line app/vmalert/web.qtpl:226
qw422016.E().S(typeK)
//line app/vmalert/web.qtpl:225
//line app/vmalert/web.qtpl:226
qw422016.N().S(`">
<span class="anchor" id="notifiers-`)
//line app/vmalert/web.qtpl:226
//line app/vmalert/web.qtpl:227
qw422016.E().S(typeK)
//line app/vmalert/web.qtpl:226
//line app/vmalert/web.qtpl:227
qw422016.N().S(`"></span>
<a href="#notifiers-`)
//line app/vmalert/web.qtpl:227
//line app/vmalert/web.qtpl:228
qw422016.E().S(typeK)
//line app/vmalert/web.qtpl:227
//line app/vmalert/web.qtpl:228
qw422016.N().S(`">`)
//line app/vmalert/web.qtpl:227
//line app/vmalert/web.qtpl:228
qw422016.E().S(typeK)
//line app/vmalert/web.qtpl:227
//line app/vmalert/web.qtpl:228
qw422016.N().S(` (`)
//line app/vmalert/web.qtpl:227
//line app/vmalert/web.qtpl:228
qw422016.N().D(count)
//line app/vmalert/web.qtpl:227
//line app/vmalert/web.qtpl:228
qw422016.N().S(`)</a>
</div>
<div class="collapse show" id="notifiers-`)
//line app/vmalert/web.qtpl:229
//line app/vmalert/web.qtpl:230
qw422016.E().S(typeK)
//line app/vmalert/web.qtpl:229
//line app/vmalert/web.qtpl:230
qw422016.N().S(`">
<table class="table table-striped table-hover table-sm">
<thead>
@ -772,113 +778,119 @@ func StreamListTargets(qw422016 *qt422016.Writer, r *http.Request, targets map[n
</thead>
<tbody>
`)
//line app/vmalert/web.qtpl:238
//line app/vmalert/web.qtpl:239
for _, n := range ns {
//line app/vmalert/web.qtpl:238
//line app/vmalert/web.qtpl:239
qw422016.N().S(`
<tr>
<td>
`)
//line app/vmalert/web.qtpl:241
//line app/vmalert/web.qtpl:242
for _, l := range n.Labels {
//line app/vmalert/web.qtpl:241
//line app/vmalert/web.qtpl:242
qw422016.N().S(`
<span class="ms-1 badge bg-primary">`)
//line app/vmalert/web.qtpl:242
//line app/vmalert/web.qtpl:243
qw422016.E().S(l.Name)
//line app/vmalert/web.qtpl:242
//line app/vmalert/web.qtpl:243
qw422016.N().S(`=`)
//line app/vmalert/web.qtpl:242
//line app/vmalert/web.qtpl:243
qw422016.E().S(l.Value)
//line app/vmalert/web.qtpl:242
//line app/vmalert/web.qtpl:243
qw422016.N().S(`</span>
`)
//line app/vmalert/web.qtpl:243
//line app/vmalert/web.qtpl:244
}
//line app/vmalert/web.qtpl:243
//line app/vmalert/web.qtpl:244
qw422016.N().S(`
</td>
<td>`)
//line app/vmalert/web.qtpl:245
//line app/vmalert/web.qtpl:246
qw422016.E().S(n.Notifier.Addr())
//line app/vmalert/web.qtpl:245
//line app/vmalert/web.qtpl:246
qw422016.N().S(`</td>
</tr>
`)
//line app/vmalert/web.qtpl:247
//line app/vmalert/web.qtpl:248
}
//line app/vmalert/web.qtpl:247
//line app/vmalert/web.qtpl:248
qw422016.N().S(`
</tbody>
</table>
</div>
`)
//line app/vmalert/web.qtpl:251
//line app/vmalert/web.qtpl:252
}
//line app/vmalert/web.qtpl:251
//line app/vmalert/web.qtpl:252
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:253
//line app/vmalert/web.qtpl:254
} else {
//line app/vmalert/web.qtpl:253
//line app/vmalert/web.qtpl:254
qw422016.N().S(`
<div>
<p>No items...</p>
</div>
`)
//line app/vmalert/web.qtpl:257
//line app/vmalert/web.qtpl:258
}
//line app/vmalert/web.qtpl:257
//line app/vmalert/web.qtpl:258
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:259
//line app/vmalert/web.qtpl:260
tpl.StreamFooter(qw422016, r)
//line app/vmalert/web.qtpl:259
//line app/vmalert/web.qtpl:260
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:261
//line app/vmalert/web.qtpl:262
}
//line app/vmalert/web.qtpl:261
//line app/vmalert/web.qtpl:262
func WriteListTargets(qq422016 qtio422016.Writer, r *http.Request, targets map[notifier.TargetType][]notifier.Target) {
//line app/vmalert/web.qtpl:261
//line app/vmalert/web.qtpl:262
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/web.qtpl:261
//line app/vmalert/web.qtpl:262
StreamListTargets(qw422016, r, targets)
//line app/vmalert/web.qtpl:261
//line app/vmalert/web.qtpl:262
qt422016.ReleaseWriter(qw422016)
//line app/vmalert/web.qtpl:261
//line app/vmalert/web.qtpl:262
}
//line app/vmalert/web.qtpl:261
//line app/vmalert/web.qtpl:262
func ListTargets(r *http.Request, targets map[notifier.TargetType][]notifier.Target) string {
//line app/vmalert/web.qtpl:261
//line app/vmalert/web.qtpl:262
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/web.qtpl:261
//line app/vmalert/web.qtpl:262
WriteListTargets(qb422016, r, targets)
//line app/vmalert/web.qtpl:261
//line app/vmalert/web.qtpl:262
qs422016 := string(qb422016.B)
//line app/vmalert/web.qtpl:261
//line app/vmalert/web.qtpl:262
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/web.qtpl:261
//line app/vmalert/web.qtpl:262
return qs422016
//line app/vmalert/web.qtpl:261
//line app/vmalert/web.qtpl:262
}
//line app/vmalert/web.qtpl:263
//line app/vmalert/web.qtpl:264
func StreamAlert(qw422016 *qt422016.Writer, r *http.Request, alert *APIAlert) {
//line app/vmalert/web.qtpl:263
//line app/vmalert/web.qtpl:264
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:264
tpl.StreamHeader(qw422016, r, navItems, "")
//line app/vmalert/web.qtpl:264
//line app/vmalert/web.qtpl:265
prefix := utils.Prefix(r.URL.Path)
//line app/vmalert/web.qtpl:265
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:266
tpl.StreamHeader(qw422016, r, navItems, "")
//line app/vmalert/web.qtpl:266
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:268
var labelKeys []string
for k := range alert.Labels {
labelKeys = append(labelKeys, k)
@ -891,28 +903,28 @@ func StreamAlert(qw422016 *qt422016.Writer, r *http.Request, alert *APIAlert) {
}
sort.Strings(annotationKeys)
//line app/vmalert/web.qtpl:277
//line app/vmalert/web.qtpl:279
qw422016.N().S(`
<div class="display-6 pb-3 mb-3">`)
//line app/vmalert/web.qtpl:278
//line app/vmalert/web.qtpl:280
qw422016.E().S(alert.Name)
//line app/vmalert/web.qtpl:278
//line app/vmalert/web.qtpl:280
qw422016.N().S(`<span class="ms-2 badge `)
//line app/vmalert/web.qtpl:278
//line app/vmalert/web.qtpl:280
if alert.State == "firing" {
//line app/vmalert/web.qtpl:278
//line app/vmalert/web.qtpl:280
qw422016.N().S(`bg-danger`)
//line app/vmalert/web.qtpl:278
//line app/vmalert/web.qtpl:280
} else {
//line app/vmalert/web.qtpl:278
//line app/vmalert/web.qtpl:280
qw422016.N().S(` bg-warning text-dark`)
//line app/vmalert/web.qtpl:278
//line app/vmalert/web.qtpl:280
}
//line app/vmalert/web.qtpl:278
//line app/vmalert/web.qtpl:280
qw422016.N().S(`">`)
//line app/vmalert/web.qtpl:278
//line app/vmalert/web.qtpl:280
qw422016.E().S(alert.State)
//line app/vmalert/web.qtpl:278
//line app/vmalert/web.qtpl:280
qw422016.N().S(`</span></div>
<div class="container border-bottom p-2">
<div class="row">
@ -921,9 +933,9 @@ func StreamAlert(qw422016 *qt422016.Writer, r *http.Request, alert *APIAlert) {
</div>
<div class="col">
`)
//line app/vmalert/web.qtpl:285
//line app/vmalert/web.qtpl:287
qw422016.E().S(alert.ActiveAt.Format("2006-01-02T15:04:05Z07:00"))
//line app/vmalert/web.qtpl:285
//line app/vmalert/web.qtpl:287
qw422016.N().S(`
</div>
</div>
@ -935,9 +947,9 @@ func StreamAlert(qw422016 *qt422016.Writer, r *http.Request, alert *APIAlert) {
</div>
<div class="col">
<code><pre>`)
//line app/vmalert/web.qtpl:295
//line app/vmalert/web.qtpl:297
qw422016.E().S(alert.Expression)
//line app/vmalert/web.qtpl:295
//line app/vmalert/web.qtpl:297
qw422016.N().S(`</pre></code>
</div>
</div>
@ -949,23 +961,23 @@ func StreamAlert(qw422016 *qt422016.Writer, r *http.Request, alert *APIAlert) {
</div>
<div class="col">
`)
//line app/vmalert/web.qtpl:305
//line app/vmalert/web.qtpl:307
for _, k := range labelKeys {
//line app/vmalert/web.qtpl:305
//line app/vmalert/web.qtpl:307
qw422016.N().S(`
<span class="m-1 badge bg-primary">`)
//line app/vmalert/web.qtpl:306
//line app/vmalert/web.qtpl:308
qw422016.E().S(k)
//line app/vmalert/web.qtpl:306
//line app/vmalert/web.qtpl:308
qw422016.N().S(`=`)
//line app/vmalert/web.qtpl:306
//line app/vmalert/web.qtpl:308
qw422016.E().S(alert.Labels[k])
//line app/vmalert/web.qtpl:306
//line app/vmalert/web.qtpl:308
qw422016.N().S(`</span>
`)
//line app/vmalert/web.qtpl:307
//line app/vmalert/web.qtpl:309
}
//line app/vmalert/web.qtpl:307
//line app/vmalert/web.qtpl:309
qw422016.N().S(`
</div>
</div>
@ -977,24 +989,24 @@ func StreamAlert(qw422016 *qt422016.Writer, r *http.Request, alert *APIAlert) {
</div>
<div class="col">
`)
//line app/vmalert/web.qtpl:317
//line app/vmalert/web.qtpl:319
for _, k := range annotationKeys {
//line app/vmalert/web.qtpl:317
//line app/vmalert/web.qtpl:319
qw422016.N().S(`
<b>`)
//line app/vmalert/web.qtpl:318
//line app/vmalert/web.qtpl:320
qw422016.E().S(k)
//line app/vmalert/web.qtpl:318
//line app/vmalert/web.qtpl:320
qw422016.N().S(`:</b><br>
<p>`)
//line app/vmalert/web.qtpl:319
//line app/vmalert/web.qtpl:321
qw422016.E().S(alert.Annotations[k])
//line app/vmalert/web.qtpl:319
//line app/vmalert/web.qtpl:321
qw422016.N().S(`</p>
`)
//line app/vmalert/web.qtpl:320
//line app/vmalert/web.qtpl:322
}
//line app/vmalert/web.qtpl:320
//line app/vmalert/web.qtpl:322
qw422016.N().S(`
</div>
</div>
@ -1005,14 +1017,18 @@ func StreamAlert(qw422016 *qt422016.Writer, r *http.Request, alert *APIAlert) {
Group
</div>
<div class="col">
<a target="_blank" href="/groups#group-`)
//line app/vmalert/web.qtpl:330
<a target="_blank" href="`)
//line app/vmalert/web.qtpl:332
qw422016.E().S(prefix)
//line app/vmalert/web.qtpl:332
qw422016.N().S(`groups#group-`)
//line app/vmalert/web.qtpl:332
qw422016.E().S(alert.GroupID)
//line app/vmalert/web.qtpl:330
//line app/vmalert/web.qtpl:332
qw422016.N().S(`">`)
//line app/vmalert/web.qtpl:330
//line app/vmalert/web.qtpl:332
qw422016.E().S(alert.GroupID)
//line app/vmalert/web.qtpl:330
//line app/vmalert/web.qtpl:332
qw422016.N().S(`</a>
</div>
</div>
@ -1024,132 +1040,132 @@ func StreamAlert(qw422016 *qt422016.Writer, r *http.Request, alert *APIAlert) {
</div>
<div class="col">
<a target="_blank" href="`)
//line app/vmalert/web.qtpl:340
//line app/vmalert/web.qtpl:342
qw422016.E().S(alert.SourceLink)
//line app/vmalert/web.qtpl:340
//line app/vmalert/web.qtpl:342
qw422016.N().S(`">Link</a>
</div>
</div>
</div>
`)
//line app/vmalert/web.qtpl:344
//line app/vmalert/web.qtpl:346
tpl.StreamFooter(qw422016, r)
//line app/vmalert/web.qtpl:344
//line app/vmalert/web.qtpl:346
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:346
//line app/vmalert/web.qtpl:348
}
//line app/vmalert/web.qtpl:346
//line app/vmalert/web.qtpl:348
func WriteAlert(qq422016 qtio422016.Writer, r *http.Request, alert *APIAlert) {
//line app/vmalert/web.qtpl:346
//line app/vmalert/web.qtpl:348
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/web.qtpl:346
//line app/vmalert/web.qtpl:348
StreamAlert(qw422016, r, alert)
//line app/vmalert/web.qtpl:346
//line app/vmalert/web.qtpl:348
qt422016.ReleaseWriter(qw422016)
//line app/vmalert/web.qtpl:346
//line app/vmalert/web.qtpl:348
}
//line app/vmalert/web.qtpl:346
//line app/vmalert/web.qtpl:348
func Alert(r *http.Request, alert *APIAlert) string {
//line app/vmalert/web.qtpl:346
//line app/vmalert/web.qtpl:348
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/web.qtpl:346
//line app/vmalert/web.qtpl:348
WriteAlert(qb422016, r, alert)
//line app/vmalert/web.qtpl:346
//line app/vmalert/web.qtpl:348
qs422016 := string(qb422016.B)
//line app/vmalert/web.qtpl:346
//line app/vmalert/web.qtpl:348
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/web.qtpl:346
//line app/vmalert/web.qtpl:348
return qs422016
//line app/vmalert/web.qtpl:346
//line app/vmalert/web.qtpl:348
}
//line app/vmalert/web.qtpl:348
//line app/vmalert/web.qtpl:350
func streambadgeState(qw422016 *qt422016.Writer, state string) {
//line app/vmalert/web.qtpl:348
//line app/vmalert/web.qtpl:350
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:350
//line app/vmalert/web.qtpl:352
badgeClass := "bg-warning text-dark"
if state == "firing" {
badgeClass = "bg-danger"
}
//line app/vmalert/web.qtpl:354
//line app/vmalert/web.qtpl:356
qw422016.N().S(`
<span class="badge `)
//line app/vmalert/web.qtpl:355
//line app/vmalert/web.qtpl:357
qw422016.E().S(badgeClass)
//line app/vmalert/web.qtpl:355
//line app/vmalert/web.qtpl:357
qw422016.N().S(`">`)
//line app/vmalert/web.qtpl:355
//line app/vmalert/web.qtpl:357
qw422016.E().S(state)
//line app/vmalert/web.qtpl:355
//line app/vmalert/web.qtpl:357
qw422016.N().S(`</span>
`)
//line app/vmalert/web.qtpl:356
//line app/vmalert/web.qtpl:358
}
//line app/vmalert/web.qtpl:356
//line app/vmalert/web.qtpl:358
func writebadgeState(qq422016 qtio422016.Writer, state string) {
//line app/vmalert/web.qtpl:356
//line app/vmalert/web.qtpl:358
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/web.qtpl:356
//line app/vmalert/web.qtpl:358
streambadgeState(qw422016, state)
//line app/vmalert/web.qtpl:356
//line app/vmalert/web.qtpl:358
qt422016.ReleaseWriter(qw422016)
//line app/vmalert/web.qtpl:356
//line app/vmalert/web.qtpl:358
}
//line app/vmalert/web.qtpl:356
//line app/vmalert/web.qtpl:358
func badgeState(state string) string {
//line app/vmalert/web.qtpl:356
//line app/vmalert/web.qtpl:358
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/web.qtpl:356
//line app/vmalert/web.qtpl:358
writebadgeState(qb422016, state)
//line app/vmalert/web.qtpl:356
//line app/vmalert/web.qtpl:358
qs422016 := string(qb422016.B)
//line app/vmalert/web.qtpl:356
//line app/vmalert/web.qtpl:358
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/web.qtpl:356
//line app/vmalert/web.qtpl:358
return qs422016
//line app/vmalert/web.qtpl:356
//line app/vmalert/web.qtpl:358
}
//line app/vmalert/web.qtpl:358
//line app/vmalert/web.qtpl:360
func streambadgeRestored(qw422016 *qt422016.Writer) {
//line app/vmalert/web.qtpl:358
//line app/vmalert/web.qtpl:360
qw422016.N().S(`
<span class="badge bg-warning text-dark" title="Alert state was restored after the service restart from remote storage">restored</span>
`)
//line app/vmalert/web.qtpl:360
//line app/vmalert/web.qtpl:362
}
//line app/vmalert/web.qtpl:360
//line app/vmalert/web.qtpl:362
func writebadgeRestored(qq422016 qtio422016.Writer) {
//line app/vmalert/web.qtpl:360
//line app/vmalert/web.qtpl:362
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/web.qtpl:360
//line app/vmalert/web.qtpl:362
streambadgeRestored(qw422016)
//line app/vmalert/web.qtpl:360
//line app/vmalert/web.qtpl:362
qt422016.ReleaseWriter(qw422016)
//line app/vmalert/web.qtpl:360
//line app/vmalert/web.qtpl:362
}
//line app/vmalert/web.qtpl:360
//line app/vmalert/web.qtpl:362
func badgeRestored() string {
//line app/vmalert/web.qtpl:360
//line app/vmalert/web.qtpl:362
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/web.qtpl:360
//line app/vmalert/web.qtpl:362
writebadgeRestored(qb422016)
//line app/vmalert/web.qtpl:360
//line app/vmalert/web.qtpl:362
qs422016 := string(qb422016.B)
//line app/vmalert/web.qtpl:360
//line app/vmalert/web.qtpl:362
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/web.qtpl:360
//line app/vmalert/web.qtpl:362
return qs422016
//line app/vmalert/web.qtpl:360
//line app/vmalert/web.qtpl:362
}

View file

@ -2,6 +2,7 @@ package main
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"reflect"
@ -29,7 +30,7 @@ func TestHandler(t *testing.T) {
t.Helper()
resp, err := http.Get(url)
if err != nil {
t.Errorf("unexpected err %s", err)
t.Fatalf("unexpected err %s", err)
}
if code != resp.StatusCode {
t.Errorf("unexpected status code %d want %d", resp.StatusCode, code)
@ -47,20 +48,72 @@ func TestHandler(t *testing.T) {
}
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { rh.handler(w, r) }))
defer ts.Close()
t.Run("/", func(t *testing.T) {
getResp(ts.URL, nil, 200)
getResp(ts.URL+"/vmalert", nil, 200)
getResp(ts.URL+"/vmalert/home", nil, 200)
})
t.Run("/api/v1/alerts", func(t *testing.T) {
lr := listAlertsResponse{}
getResp(ts.URL+"/api/v1/alerts", &lr, 200)
if length := len(lr.Data.Alerts); length != 1 {
t.Errorf("expected 1 alert got %d", length)
}
lr = listAlertsResponse{}
getResp(ts.URL+"/vmalert/api/v1/alerts", &lr, 200)
if length := len(lr.Data.Alerts); length != 1 {
t.Errorf("expected 1 alert got %d", length)
}
})
t.Run("/api/v1/alert?alertID&groupID", func(t *testing.T) {
expAlert := ar.newAlertAPI(*ar.alerts[0])
alert := &APIAlert{}
getResp(ts.URL+"/"+expAlert.APILink(), alert, 200)
if !reflect.DeepEqual(alert, expAlert) {
t.Errorf("expected %v is equal to %v", alert, expAlert)
}
alert = &APIAlert{}
getResp(ts.URL+"/vmalert/"+expAlert.APILink(), alert, 200)
if !reflect.DeepEqual(alert, expAlert) {
t.Errorf("expected %v is equal to %v", alert, expAlert)
}
})
t.Run("/api/v1/alert?badParams", func(t *testing.T) {
params := fmt.Sprintf("?%s=0&%s=1", paramGroupID, paramAlertID)
getResp(ts.URL+"/api/v1/alert"+params, nil, 404)
getResp(ts.URL+"/vmalert/api/v1/alert"+params, nil, 404)
params = fmt.Sprintf("?%s=1&%s=0", paramGroupID, paramAlertID)
getResp(ts.URL+"/api/v1/alert"+params, nil, 404)
getResp(ts.URL+"/vmalert/api/v1/alert"+params, nil, 404)
// bad request, alertID is missing
params = fmt.Sprintf("?%s=1", paramGroupID)
getResp(ts.URL+"/api/v1/alert"+params, nil, 400)
getResp(ts.URL+"/vmalert/api/v1/alert"+params, nil, 400)
})
t.Run("/api/v1/rules", func(t *testing.T) {
lr := listGroupsResponse{}
getResp(ts.URL+"/api/v1/rules", &lr, 200)
if length := len(lr.Data.Groups); length != 1 {
t.Errorf("expected 1 group got %d", length)
}
lr = listGroupsResponse{}
getResp(ts.URL+"/vmalert/api/v1/rules", &lr, 200)
if length := len(lr.Data.Groups); length != 1 {
t.Errorf("expected 1 group got %d", length)
}
})
// check deprecated links support
// TODO: remove as soon as deprecated links removed
t.Run("/api/v1/0/0/status", func(t *testing.T) {
alert := &APIAlert{}
getResp(ts.URL+"/api/v1/0/0/status", alert, 200)
@ -75,7 +128,5 @@ func TestHandler(t *testing.T) {
t.Run("/api/v1/1/0/status", func(t *testing.T) {
getResp(ts.URL+"/api/v1/1/0/status", nil, 404)
})
t.Run("/", func(t *testing.T) {
getResp(ts.URL, nil, 200)
})
}

View file

@ -1,6 +1,7 @@
package main
import (
"fmt"
"time"
)
@ -33,6 +34,18 @@ type APIAlert struct {
Restored bool `json:"restored"`
}
// WebLink returns a link to the alert which can be used in UI.
func (aa *APIAlert) WebLink() string {
return fmt.Sprintf("alert?%s=%s&%s=%s",
paramGroupID, aa.GroupID, paramAlertID, aa.ID)
}
// APILink returns a link to the alert's JSON representation.
func (aa *APIAlert) APILink() string {
return fmt.Sprintf("api/v1/alert?%s=%s&%s=%s",
paramGroupID, aa.GroupID, paramAlertID, aa.ID)
}
// APIGroup represents Group for WEB view
// https://github.com/prometheus/compliance/blob/main/alert_generator/specification.md#get-apiv1rules
type APIGroup struct {

View file

@ -556,20 +556,20 @@ func mergeSortBlocks(dst *Result, sbh sortBlocksHeap, dedupInterval int64) {
}
sbNext := sbh[0]
tsNext := sbNext.Timestamps[sbNext.NextIdx]
idxNext := len(top.Timestamps)
if top.Timestamps[idxNext-1] > tsNext {
idxNext = top.NextIdx
for top.Timestamps[idxNext] <= tsNext {
idxNext++
}
topTimestamps := top.Timestamps
topNextIdx := top.NextIdx
if n := equalTimestampsPrefix(topTimestamps[topNextIdx:], sbNext.Timestamps[sbNext.NextIdx:]); n > 0 && dedupInterval > 0 {
// Skip n replicated samples at top if deduplication is enabled.
top.NextIdx = topNextIdx + n
} else {
// Copy samples from top to dst with timestamps not exceeding tsNext.
top.NextIdx = topNextIdx + binarySearchTimestamps(topTimestamps[topNextIdx:], tsNext)
dst.Timestamps = append(dst.Timestamps, topTimestamps[topNextIdx:top.NextIdx]...)
dst.Values = append(dst.Values, top.Values[topNextIdx:top.NextIdx]...)
}
dst.Timestamps = append(dst.Timestamps, top.Timestamps[top.NextIdx:idxNext]...)
dst.Values = append(dst.Values, top.Values[top.NextIdx:idxNext]...)
if idxNext < len(top.Timestamps) {
top.NextIdx = idxNext
if top.NextIdx < len(topTimestamps) {
heap.Push(&sbh, top)
} else {
// Return top to the pool.
putSortBlock(top)
}
}
@ -582,6 +582,34 @@ func mergeSortBlocks(dst *Result, sbh sortBlocksHeap, dedupInterval int64) {
var dedupsDuringSelect = metrics.NewCounter(`vm_deduplicated_samples_total{type="select"}`)
func equalTimestampsPrefix(a, b []int64) int {
for i, v := range a {
if i >= len(b) || v != b[i] {
return i
}
}
return len(a)
}
func binarySearchTimestamps(timestamps []int64, ts int64) int {
// The code has been adapted from sort.Search.
n := len(timestamps)
if n > 0 && timestamps[n-1] <= ts {
// Fast path for timestamps scanned in ascending order.
return n
}
i, j := 0, n
for i < j {
h := int(uint(i+j) >> 1)
if h >= 0 && h < len(timestamps) && timestamps[h] <= ts {
i = h + 1
} else {
j = h
}
}
return i
}
type sortBlock struct {
Timestamps []int64
Values []float64

View file

@ -0,0 +1,179 @@
package netstorage
import (
"reflect"
"testing"
)
func TestMergeSortBlocks(t *testing.T) {
f := func(blocks []*sortBlock, dedupInterval int64, expectedResult *Result) {
t.Helper()
var result Result
mergeSortBlocks(&result, blocks, dedupInterval)
if !reflect.DeepEqual(result.Values, expectedResult.Values) {
t.Fatalf("unexpected values;\ngot\n%v\nwant\n%v", result.Values, expectedResult.Values)
}
if !reflect.DeepEqual(result.Timestamps, expectedResult.Timestamps) {
t.Fatalf("unexpected timestamps;\ngot\n%v\nwant\n%v", result.Timestamps, expectedResult.Timestamps)
}
}
// Zero blocks
f(nil, 1, &Result{})
// Single block without samples
f([]*sortBlock{{}}, 1, &Result{})
// Single block with a single samples.
f([]*sortBlock{
{
Timestamps: []int64{1},
Values: []float64{4.2},
},
}, 1, &Result{
Timestamps: []int64{1},
Values: []float64{4.2},
})
// Single block with multiple samples.
f([]*sortBlock{
{
Timestamps: []int64{1, 2, 3},
Values: []float64{4.2, 2.1, 10},
},
}, 1, &Result{
Timestamps: []int64{1, 2, 3},
Values: []float64{4.2, 2.1, 10},
})
// Single block with multiple samples with deduplication.
f([]*sortBlock{
{
Timestamps: []int64{1, 2, 3},
Values: []float64{4.2, 2.1, 10},
},
}, 2, &Result{
Timestamps: []int64{2, 3},
Values: []float64{2.1, 10},
})
// Multiple blocks without time range intersection.
f([]*sortBlock{
{
Timestamps: []int64{3, 5},
Values: []float64{5.2, 6.1},
},
{
Timestamps: []int64{1, 2},
Values: []float64{4.2, 2.1},
},
}, 1, &Result{
Timestamps: []int64{1, 2, 3, 5},
Values: []float64{4.2, 2.1, 5.2, 6.1},
})
// Multiple blocks with time range intersection.
f([]*sortBlock{
{
Timestamps: []int64{3, 5},
Values: []float64{5.2, 6.1},
},
{
Timestamps: []int64{1, 2, 4},
Values: []float64{4.2, 2.1, 42},
},
}, 1, &Result{
Timestamps: []int64{1, 2, 3, 4, 5},
Values: []float64{4.2, 2.1, 5.2, 42, 6.1},
})
// Multiple blocks with time range inclusion.
f([]*sortBlock{
{
Timestamps: []int64{0, 3, 5},
Values: []float64{9, 5.2, 6.1},
},
{
Timestamps: []int64{1, 2, 4},
Values: []float64{4.2, 2.1, 42},
},
}, 1, &Result{
Timestamps: []int64{0, 1, 2, 3, 4, 5},
Values: []float64{9, 4.2, 2.1, 5.2, 42, 6.1},
})
// Multiple blocks with identical timestamps.
f([]*sortBlock{
{
Timestamps: []int64{1, 2, 4},
Values: []float64{9, 5.2, 6.1},
},
{
Timestamps: []int64{1, 2, 4},
Values: []float64{4.2, 2.1, 42},
},
}, 1, &Result{
Timestamps: []int64{1, 2, 4},
Values: []float64{4.2, 2.1, 42},
})
// Multiple blocks with identical timestamps, disabled deduplication.
f([]*sortBlock{
{
Timestamps: []int64{1, 2, 4},
Values: []float64{9, 5.2, 6.1},
},
{
Timestamps: []int64{1, 2, 4},
Values: []float64{4.2, 2.1, 42},
},
}, 0, &Result{
Timestamps: []int64{1, 1, 2, 2, 4, 4},
Values: []float64{9, 4.2, 2.1, 5.2, 6.1, 42},
})
// Multiple blocks with identical timestamp ranges.
f([]*sortBlock{
{
Timestamps: []int64{1, 2, 5, 10, 11},
Values: []float64{9, 8, 7, 6, 5},
},
{
Timestamps: []int64{1, 2, 4, 10, 11, 12},
Values: []float64{21, 22, 23, 24, 25, 26},
},
}, 1, &Result{
Timestamps: []int64{1, 2, 4, 5, 10, 11, 12},
Values: []float64{21, 22, 23, 7, 24, 5, 26},
})
// Multiple blocks with identical timestamp ranges, no deduplication.
f([]*sortBlock{
{
Timestamps: []int64{1, 2, 5, 10, 11},
Values: []float64{9, 8, 7, 6, 5},
},
{
Timestamps: []int64{1, 2, 4, 10, 11, 12},
Values: []float64{21, 22, 23, 24, 25, 26},
},
}, 0, &Result{
Timestamps: []int64{1, 1, 2, 2, 4, 5, 10, 10, 11, 11, 12},
Values: []float64{9, 21, 22, 8, 23, 7, 6, 24, 25, 5, 26},
})
// Multiple blocks with identical timestamp ranges with deduplication.
f([]*sortBlock{
{
Timestamps: []int64{1, 2, 5, 10, 11},
Values: []float64{9, 8, 7, 6, 5},
},
{
Timestamps: []int64{1, 2, 4, 10, 11, 12},
Values: []float64{21, 22, 23, 24, 25, 26},
},
}, 5, &Result{
Timestamps: []int64{5, 10, 12},
Values: []float64{7, 24, 26},
})
}

View file

@ -0,0 +1,78 @@
package netstorage
import (
"fmt"
"testing"
)
func BenchmarkMergeSortBlocks(b *testing.B) {
for _, replicationFactor := range []int{1, 2, 3, 4, 5} {
b.Run(fmt.Sprintf("replicationFactor-%d", replicationFactor), func(b *testing.B) {
const samplesPerBlock = 8192
var blocks []*sortBlock
for j := 0; j < 10; j++ {
timestamps := make([]int64, samplesPerBlock)
values := make([]float64, samplesPerBlock)
for i := range timestamps {
timestamps[i] = int64(j*samplesPerBlock + i)
values[i] = float64(j*samplesPerBlock + i)
}
for i := 0; i < replicationFactor; i++ {
blocks = append(blocks, &sortBlock{
Timestamps: timestamps,
Values: values,
})
}
}
benchmarkMergeSortBlocks(b, blocks)
})
}
b.Run("overlapped-blocks", func(b *testing.B) {
const samplesPerBlock = 8192
var blocks []*sortBlock
for j := 0; j < 10; j++ {
timestamps := make([]int64, samplesPerBlock)
values := make([]float64, samplesPerBlock)
for i := range timestamps {
timestamps[i] = int64(j*samplesPerBlock + i)
values[i] = float64(j*samplesPerBlock + i)
}
blocks = append(blocks, &sortBlock{
Timestamps: timestamps,
Values: values,
})
}
for j := 1; j < len(blocks); j++ {
prev := blocks[j-1].Timestamps
curr := blocks[j].Timestamps
for i := 0; i < samplesPerBlock/2; i++ {
prev[i+samplesPerBlock/2], curr[i] = curr[i], prev[i+samplesPerBlock/2]
}
}
benchmarkMergeSortBlocks(b, blocks)
})
}
func benchmarkMergeSortBlocks(b *testing.B, blocks []*sortBlock) {
dedupInterval := int64(1)
samples := 0
for _, b := range blocks {
samples += len(b.Timestamps)
}
b.SetBytes(int64(samples))
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
var result Result
sbs := make(sortBlocksHeap, len(blocks))
for pb.Next() {
result.reset()
for i, b := range blocks {
sb := getSortBlock()
sb.Timestamps = b.Timestamps
sb.Values = b.Values
sbs[i] = sb
}
mergeSortBlocks(&result, sbs, dedupInterval)
}
})
}

View file

@ -214,10 +214,12 @@ func TestExecSuccess(t *testing.T) {
t.Run("timezone_offset(America/New_York)", func(t *testing.T) {
t.Parallel()
q := `timezone_offset("America/New_York")`
offset, err := getTimezoneOffset("America/New_York")
loc, err := time.LoadLocation("America/New_York")
if err != nil {
t.Fatalf("cannot obtain timezone: %s", err)
}
at := time.Unix(timestampsExpected[0]/1000, 0)
_, offset := at.In(loc).Zone()
off := float64(offset)
r := netstorage.Result{
MetricName: metricNameExpected,
@ -230,10 +232,12 @@ func TestExecSuccess(t *testing.T) {
t.Run("timezone_offset(Local)", func(t *testing.T) {
t.Parallel()
q := `timezone_offset("Local")`
offset, err := getTimezoneOffset("Local")
loc, err := time.LoadLocation("Local")
if err != nil {
t.Fatalf("cannot obtain timezone: %s", err)
}
at := time.Unix(timestampsExpected[0]/1000, 0)
_, offset := at.In(loc).Zone()
off := float64(offset)
r := netstorage.Result{
MetricName: metricNameExpected,

View file

@ -2178,21 +2178,22 @@ func transformTimezoneOffset(tfa *transformFuncArg) ([]*timeseries, error) {
if err != nil {
return nil, fmt.Errorf("cannot get timezone name: %w", err)
}
tzOffset, err := getTimezoneOffset(tzString)
if err != nil {
return nil, fmt.Errorf("cannot get timezone offset for %q: %w", tzString, err)
}
rv := evalNumber(tfa.ec, float64(tzOffset))
return rv, nil
}
func getTimezoneOffset(tzString string) (int, error) {
loc, err := time.LoadLocation(tzString)
if err != nil {
return 0, fmt.Errorf("cannot load timezone %q: %w", tzString, err)
return nil, fmt.Errorf("cannot load timezone %q: %w", tzString, err)
}
_, tzOffset := time.Now().In(loc).Zone()
return tzOffset, nil
var ts timeseries
ts.denyReuse = true
timestamps := tfa.ec.getSharedTimestamps()
values := make([]float64, len(timestamps))
for i, v := range timestamps {
_, offset := time.Unix(v/1000, 0).In(loc).Zone()
values[i] = float64(offset)
}
ts.Values = values
ts.Timestamps = timestamps
return []*timeseries{&ts}, nil
}
func transformTime(tfa *transformFuncArg) ([]*timeseries, error) {

View file

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

View file

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

View file

@ -4,6 +4,6 @@ export const getQueryRangeUrl = (server: string, query: string, period: TimePara
`${server}/api/v1/query_range?query=${encodeURIComponent(query)}&start=${period.start}&end=${period.end}&step=${period.step}${nocache ? "&nocache=1" : ""}${queryTracing ? "&trace=1" : ""}`;
export const getQueryUrl = (server: string, query: string, period: TimeParams, queryTracing: boolean): string =>
`${server}/api/v1/query?query=${encodeURIComponent(query)}&start=${period.start}&end=${period.end}&step=${period.step}${queryTracing ? "&trace=1" : ""}`;
`${server}/api/v1/query?query=${encodeURIComponent(query)}&time=${period.end}&step=${period.step}${queryTracing ? "&trace=1" : ""}`;
export const getQueryOptions = (server: string) => `${server}/api/v1/label/__name__/values`;

View file

@ -15,10 +15,11 @@ The following tip changes can be tested by building VictoriaMetrics components f
## tip
**Update notes:** this release introduces backwards-incompatible changes to `vm_partial_results_total` metric by changing its labels to be consistent with `vm_requests_total` metric.
If you use alerting rules or Grafana dashboards, which rely on this metric, then they must be updated. The official dashboards for VictoriaMetrics don't use this metric.
**Update note1:** this release introduces backwards-incompatible changes to `vm_partial_results_total` metric by changing its labels to be consistent with `vm_requests_total` metric. If you use alerting rules or Grafana dashboards, which rely on this metric, then they must be updated. The official dashboards for VictoriaMetrics don't use this metric.
**Update note2:** [vmalert](https://docs.victoriametrics.com/vmalert.html) adds `/vmalert/` prefix to [web urls](https://docs.victoriametrics.com/vmalert.html#web) according to [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2825). This may affect `vmalert` instances with non-empty `-http.pathPrefix` command-line flag. After the update, configuring this flag is no longer needed. Here's [why](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2799#issuecomment-1171392005).
* FEATURE: [cluster version of VictoriaMetrics](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): add support for querying lower-level `vmselect` nodes from upper-level `vmselect` nodes. This makes possible to build multi-level cluster setups for global querying view and HA purposes without the need to use [Promxy](https://github.com/jacksontj/promxy). See [these docs](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#multi-level-cluster-setup) for details.
* FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert.html): deprecate alert's status link `/api/v1/<groupID>/<alertID>/status` in favour of `api/v1/alert?group_id=<group_id>&alert_id=<alert_id>"`. The old alert's status link is still supported, but will be removed in future releases. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2825).
* FEATURE: [cluster version of VictoriaMetrics](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): add support for querying lower-level `vmselect` nodes from upper-level `vmselect` nodes. This makes possible to build multi-level cluster setups for global querying view and HA purposes without the need to use [Promxy](https://github.com/jacksontj/promxy). See [these docs](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#multi-level-cluster-setup) and [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2778).
* FEATURE: add `-search.setLookbackToStep` command-line flag, which enables InfluxDB-like gap filling during querying. See [these docs](https://docs.victoriametrics.com/guides/migrate-from-influx.html) for details.
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add an UI for [query tracing](https://docs.victoriametrics.com/#query-tracing). It can be enabled by clicking `trace query` checkbox and re-running the query. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2703).
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add `-remoteWrite.headers` command-line option for specifying optional HTTP headers to send to the configured `-remoteWrite.url`. For example, `-remoteWrite.headers='Foo:Bar^^Baz:x'` would send `Foo: Bar` and `Baz: x` HTTP headers with every request to `-remoteWrite.url`. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2805).
@ -38,6 +39,7 @@ scrape_configs:
* FEATURE: [query tracing](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#query-tracing): show timestamps in query traces in human-readable format (aka `RFC3339` in UTC timezone) instead of milliseconds since Unix epoch. For example, `2022-06-27T10:32:54.506Z` instead of `1656325974506`. This improves traces' readability.
* FEATURE: improve performance of [/api/v1/series](https://prometheus.io/docs/prometheus/latest/querying/api/#finding-series-by-label-matchers) requests, which return big number of time series.
* FEATURE: [VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): improve query performance when [replication is enabled](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#replication-and-data-safety).
* FEATURE: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): properly handle partial counter resets in [remove_resets](https://docs.victoriametrics.com/MetricsQL.html#remove_resets) function. Now `remove_resets(sum(m))` should returns the expected increasing line when some time series matching `m` disappear on the selected time range. Previously such a query would return horizontal line after the disappeared series.
* FEATURE: expose additional histogram metrics at `http://victoriametrics:8428/metrics`, which may help understanding query workload:
@ -46,7 +48,6 @@ scrape_configs:
* `vm_rows_read_per_series` - the number of raw samples read per queried series.
* `vm_series_read_per_query` - the number of series read per query.
* BUGFIX: properly register time series in per-day inverted index. Previously some series could miss registration in the per-day inverted index. This could result in missing time series during querying. The issue has been introduced in [v1.78.0](#v1780). See [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2798) and [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2793) issues.
* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): allow using `__name__` label (aka [metric name](https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors)) in alerting annotations. For example:
{% raw %}
@ -58,6 +59,7 @@ scrape_configs:
* BUGFIX: limit max memory occupied by the cache, which stores parsed regular expressions. Previously too long regular expressions passed in [MetricsQL queries](https://docs.victoriametrics.com/MetricsQL.html) could result in big amounts of used memory (e.g. multiple of gigabytes). Now the max cache size for parsed regexps is limited to a a few megabytes.
* BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): properly handle partial counter resets when calculating [rate](https://docs.victoriametrics.com/MetricsQL.html#rate), [irate](https://docs.victoriametrics.com/MetricsQL.html#irate) and [increase](https://docs.victoriametrics.com/MetricsQL.html#increase) functions. Previously these functions could return zero values after partial counter resets until the counter increases to the last value before partial counter reset. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2787).
* BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): properly calculate [histogram_quantile](https://docs.victoriametrics.com/MetricsQL.html#histogram_quantile) over Prometheus buckets with unexpected values. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2819).
* BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): properly evaluate [timezone_offset](https://docs.victoriametrics.com/MetricsQL.html#timezone_offset) function over time range covering time zone offset switches. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2771).
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): properly add service-level labels (`__meta_kubernetes_service_*`) to discovered targets for `role: endpointslice` in [kubernetes_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config). Previously these labels were missing. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2823).
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): make sure that [stale markers](https://docs.victoriametrics.com/vmagent.html#prometheus-staleness-markers) are generated with the actual timestamp when unsuccessful scrape occurs. This should prevent from possible time series overlap on scrape target restart in dynmaic envirnoments such as Kubernetes.
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): properly reload changed `-promscrape.config` file when `-promscrape.configCheckInterval` option is set. The changed config file wasn't reloaded in this case since [v1.69.0](#v1690). See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/2786). Thanks to @ttyv for the fix.
@ -65,14 +67,21 @@ scrape_configs:
* BUGFIX: [VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): assume that the response is complete if `-search.denyPartialResponse` is enabled and up to `-replicationFactor - 1` `vmstorage` nodes are unavailable. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1767).
* BUGFIX: [vmselect](https://docs.victoriametrics.com/#vmselect): update `vm_partial_results_total` metric labels to be consistent with `vm_requests_total` labels.
* BUGFIX: accept tags without values when reading data in [DataDog format](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#how-to-send-data-from-datadog-agent). Thanks to @PerGon for the [pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/2839).
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): properly pass the end of the selected time range to `time` query arg to [/api/v1/query](https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries) when displaying the requested data in JSON and table views. Previously the `time` query arg wasn't set, so `/api/v1/query` was always returning query results for the current time regardless of the selected time range. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2781).
## [v1.78.1](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.78.1)
Released at 08-07-2022
**Update notes:** it is recommended [clearing caches](https://docs.victoriametrics.com/#cache-removal) after the upgrade from [v1.78.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.78.0) in order to immediately fix the issue for newly ingested data. Otherwise the issue may exist for newly ingested data for up to a day after the upgrade.
* BUGFIX: properly register time series in per-day inverted index. Previously some series could miss registration in the per-day inverted index. This could result in missing time series during querying. The issue has been introduced in [v1.78.0](#v1780). See [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2798) and [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2793) issues.
## [v1.78.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.78.0)
Released at 20-06-2022
**Warning (03-07-2022):** VictoriaMetrics v1.78.0 contains a bug, which may result in missing time series during queries.
It is recommended downgrading to [v1.77.2](#v1772) until the bugfix release.
**Warning (03-07-2022):** VictoriaMetrics v1.78.0 contains a bug, which may result in missing time series during queries. It is recommended upgrading to [v1.78.1](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.78.1), which fixes the bug.
**Update notes:** this release introduces backwards-incompatible changes to communication protocol between `vmselect` and `vmstorage` nodes in cluster version of VictoriaMetrics because of added [query tracing](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#query-tracing), so `vmselect` and `vmstorage` nodes will experience communication errors and read requests to `vmselect` will fail until the upgrade is complete. These errors will stop after all the `vmselect` and `vmstorage` nodes are updated to the new release. It is safe to downgrade to previous releases.

View file

@ -6,7 +6,7 @@ sort: 3
`vmagent` is a tiny but mighty agent which helps you collect metrics from various sources
and store them in [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)
or any other Prometheus-compatible storage systems that support the `remote_write` protocol.
or any other Prometheus-compatible storage systems with Prometheus `remote_write` protocol support.
<img alt="vmagent" src="vmagent.png">
@ -15,7 +15,8 @@ or any other Prometheus-compatible storage systems that support the `remote_writ
While VictoriaMetrics provides an efficient solution to store and observe metrics, our users needed something fast
and RAM friendly to scrape metrics from Prometheus-compatible exporters into VictoriaMetrics.
Also, we found that our user's infrastructure are like snowflakes in that no two are alike. Therefore we decided to add more flexibility
to `vmagent` such as the ability to push metrics additionally to pulling them. We did our best and will continue to improve `vmagent`.
to `vmagent` such as the ability to [accept metrics via popular push protocols](#how-to-push-data-to-vmagent)
additionally to [discovering Prometheus-compatible targets and scraping metrics from them](#how-to-collect-metrics-in-prometheus-format).
## Features
@ -93,20 +94,24 @@ There is also `-promscrape.configCheckInterval` command-line option, which can b
### IoT and Edge monitoring
`vmagent` can run and collect metrics in IoT and industrial networks with unreliable or scheduled connections to their remote storage.
`vmagent` can run and collect metrics in IoT environments and industrial networks with unreliable or scheduled connections to their remote storage.
It buffers the collected data in local files until the connection to remote storage becomes available and then sends the buffered
data to the remote storage. It re-tries sending the data to remote storage until any errors are resolved.
The maximum buffer size can be limited with `-remoteWrite.maxDiskUsagePerURL`.
data to the remote storage. It re-tries sending the data to remote storage until errors are resolved.
The maximum on-disk size for the buffered metrics can be limited with `-remoteWrite.maxDiskUsagePerURL`.
`vmagent` works on various architectures from the IoT world - 32-bit arm, 64-bit arm, ppc64, 386, amd64.
See [the corresponding Makefile rules](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmagent/Makefile) for details.
### Drop-in replacement for Prometheus
If you use Prometheus only for scraping metrics from various targets and forwarding those metrics to remote storage
If you use Prometheus only for scraping metrics from various targets and forwarding these metrics to remote storage
then `vmagent` can replace Prometheus. Typically, `vmagent` requires lower amounts of RAM, CPU and network bandwidth compared with Prometheus.
See [these docs](#how-to-collect-metrics-in-prometheus-format) for details.
### Flexible metrics relay
`vmagent` can accept metrics in [various popular data ingestion protocols](#how-to-push-data-to-vmagent), apply [relabeling](#relabeling) to the accepted metrics (for example, change metric names/labels or drop unneeded metrics) and then forward the relabeled metrics to other remote storage systems, which support Prometheus `remote_write` protocol (including other `vmagent` instances).
### Replication and high availability
`vmagent` replicates the collected metrics among multiple remote storage instances configured via `-remoteWrite.url` args.
@ -150,7 +155,7 @@ sections from [Prometheus config file](https://prometheus.io/docs/prometheus/lat
* `scrape_configs`
All other sections are ignored, including the [remote_write](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write) section.
Use `-remoteWrite.*` command-line flag instead for configuring remote write settings. See [the list of unsupported config sections](##unsupported-prometheus-config-sections).
Use `-remoteWrite.*` command-line flag instead for configuring remote write settings. See [the list of unsupported config sections](#unsupported-prometheus-config-sections).
The file pointed by `-promscrape.config` may contain `%{ENV_VAR}` placeholders which are substituted by the corresponding `ENV_VAR` environment variable values.
@ -180,6 +185,8 @@ entries to 60s. Run `vmagent -help` in order to see default values for the `-pro
Please file feature requests to [our issue tracker](https://github.com/VictoriaMetrics/VictoriaMetrics/issues) if you need other service discovery mechanisms to be supported by `vmagent`.
## scrape_config enhancements
`vmagent` supports the following additional options in `scrape_configs` section:
* `headers` - a list of HTTP headers to send to scrape target with each scrape request. This can be used when the scrape target needs custom authorization and authentication. For example:
@ -192,7 +199,7 @@ scrape_configs:
- "My-Auth: TopSecret"
```
* `disable_compression: true` for disableing response compression on a per-job basis. By default `vmagent` requests compressed responses from scrape targets for saving network bandwidth.
* `disable_compression: true` for disabling response compression on a per-job basis. By default `vmagent` requests compressed responses from scrape targets for saving network bandwidth.
* `disable_keepalive: true` for disabling [HTTP keep-alive connections](https://en.wikipedia.org/wiki/HTTP_persistent_connection) on a per-job basis. By default `vmagent` uses keep-alive connections to scrape targets for reducing overhead on connection re-establishing.
* `series_limit: N` for limiting the number of unique time series a single scrape target can expose. See [these docs](#cardinality-limiter).
* `stream_parse: true` for scraping targets in a streaming manner. This may be useful when targets export big number of metrics. See [these docs](#stream-parsing-mode).
@ -261,7 +268,7 @@ Extra labels can be added to metrics collected by `vmagent` via the following me
up == 0
```
* `scrape_duration_seconds` - this metric exposes scrape duration. This allows monitoring slow scrapes. For example, the following [MetricsQL query](https://docs.victoriametrics.com/MetricsQL.html) returns scrapes, which took more than 1.5 seconds:
* `scrape_duration_seconds` - this metric exposes scrape duration. This allows monitoring slow scrapes. For example, the following [MetricsQL query](https://docs.victoriametrics.com/MetricsQL.html) returns scrapes, which take more than 1.5 seconds to complete:
```metricsql
scrape_duration_seconds > 1.5
@ -273,7 +280,7 @@ Extra labels can be added to metrics collected by `vmagent` via the following me
scrape_duration_seconds / scrape_timeout_seconds > 0.8
```
* `scrape_samples_scraped` - this metric exposes the number of samples (aka metrics) collected per each scrape. This allows detecting targets, which expose too many metrics. For example, the following [MetricsQL query](https://docs.victoriametrics.com/MetricsQL.html) returns targets, which expose more than 10000 metrics:
* `scrape_samples_scraped` - this metric exposes the number of samples (aka metrics) parsed per each scrape. This allows detecting targets, which expose too many metrics. For example, the following [MetricsQL query](https://docs.victoriametrics.com/MetricsQL.html) returns targets, which expose more than 10000 metrics:
```metricsql
scrape_samples_scraped > 10000
@ -305,7 +312,7 @@ Extra labels can be added to metrics collected by `vmagent` via the following me
VictoriaMetrics components (including `vmagent`) support [Prometheus-compatible relabeling](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config) with [additional enhancements](#relabeling-enhancements) at various stages of data processing. The relabeling can be defined in the following places processed by `vmagent`:
* At the `scrape_config -> relabel_configs` section in `-promscrape.config` file. This relabeling is used for modifying labels in discovered targets and for dropping unneded targets. This relabeling can be debugged by passing `relabel_debug: true` option to the corresponding `scrape_config` section. In this case `vmagent` logs target labels before and after the relabeling and then drops the logged target.
* At the `scrape_config -> metric_relabel_configs` section in `-promscrape.config` file. This relabeling is used for modifying labels in scraped target metrics and for dropping unneeded metrics. This relabeling can be debugged by passing `metric_relabel_debug: true` option to the corresponding `scrape_config` section. In this case `vmagent` logs metrics before and after the relabeling and then drops the logged metrics.
* At the `scrape_config -> metric_relabel_configs` section in `-promscrape.config` file. This relabeling is used for modifying labels in scraped metrics and for dropping unneeded metrics. This relabeling can be debugged by passing `metric_relabel_debug: true` option to the corresponding `scrape_config` section. In this case `vmagent` logs metrics before and after the relabeling and then drops the logged metrics.
* At the `-remoteWrite.relabelConfig` file. This relabeling is used for modifying labels for all the collected metrics (inluding [metrics obtained via push-based protocols](#how-to-push-data-to-vmagent)) and for dropping unneeded metrics before sending them to all the configured `-remoteWrite.url` addresses. This relabeling can be debugged by passing `-remoteWrite.relabelDebug` command-line option to `vmagent`. In this case `vmagent` logs metrics before and after the relabeling and then drops all the logged metrics instead of sending them to remote storage.
* At the `-remoteWrite.urlRelabelConfig` files. This relabeling is used for modifying labels for metrics and for dropping unneeded metrics before sending them to a particular `-remoteWrite.url`. This relabeling can be debugged by passing `-remoteWrite.urlRelabelDebug` command-line options to `vmagent`. In this case `vmagent` logs metrics before and after the relabeling and then drops all the logged metrics instead of sending them to the corresponding `-remoteWrite.url`.
@ -371,6 +378,7 @@ VictoriaMetrics provides the following additional relabeling actions on top of s
```yaml
- action: drop_metrics
regex: "foo|bar"
```
* `graphite`: applies Graphite-style relabeling to metric name. See [these docs](#graphite-relabeling) for details.

View file

@ -485,7 +485,7 @@ or time series modification via [relabeling](https://docs.victoriametrics.com/vm
* `http://<vmalert-addr>` - UI;
* `http://<vmalert-addr>/api/v1/rules` - list of all loaded groups and rules;
* `http://<vmalert-addr>/api/v1/alerts` - list of all active alerts;
* `http://<vmalert-addr>/api/v1/<groupID>/<alertID>/status"` - get alert status by ID.
* `http://<vmalert-addr>/vmalert/api/v1/alert?group_id=<group_id>&alert_id=<alert_id>"` - get alert status by ID.
Used as alert source in AlertManager.
* `http://<vmalert-addr>/metrics` - application metrics.
* `http://<vmalert-addr>/-/reload` - hot configuration reload.
@ -685,7 +685,7 @@ The shortlist of configuration flags is the following:
How often to evaluate the rules (default 1m0s)
-external.alert.source string
External Alert Source allows to override the Source link for alerts sent to AlertManager for cases where you want to build a custom link to Grafana, Prometheus or any other service.
eg. 'explore?orgId=1&left=[\"now-1h\",\"now\",\"VictoriaMetrics\",{\"expr\": \"{{$expr|quotesEscape|crlfEscape|queryEscape}}\"},{\"mode\":\"Metrics\"},{\"ui\":[true,true,true,\"none\"]}]'.If empty '/api/v1/:groupID/alertID/status' is used
eg. 'explore?orgId=1&left=[\"now-1h\",\"now\",\"VictoriaMetrics\",{\"expr\": \"{{$expr|quotesEscape|crlfEscape|queryEscape}}\"},{\"mode\":\"Metrics\"},{\"ui\":[true,true,true,\"none\"]}]'.If empty '/vmalert/api/v1/alert?group_id=&alert_id=' is used
-external.label array
Optional label in the form 'Name=value' to add to all generated recording rules and alerts. Pass multiple -label flags in order to add multiple label sets.
Supports an array of values separated by comma or specified via multiple flags.

12
go.mod
View file

@ -11,7 +11,7 @@ require (
github.com/VictoriaMetrics/fasthttp v1.1.0
github.com/VictoriaMetrics/metrics v1.18.1
github.com/VictoriaMetrics/metricsql v0.44.1
github.com/aws/aws-sdk-go v1.44.47
github.com/aws/aws-sdk-go v1.44.51
github.com/cespare/xxhash/v2 v2.1.2
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
@ -22,22 +22,22 @@ require (
github.com/fatih/color v1.13.0 // indirect
github.com/go-kit/kit v0.12.0
github.com/golang/snappy v0.0.4
github.com/influxdata/influxdb v1.9.7
github.com/influxdata/influxdb v1.9.8
github.com/klauspost/compress v1.15.7
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/oklog/ulid v1.3.1
github.com/prometheus/common v0.35.0 // indirect
github.com/prometheus/prometheus v1.8.2-0.20201119142752-3ad25a6dc3d9
github.com/urfave/cli/v2 v2.10.3
github.com/urfave/cli/v2 v2.11.0
github.com/valyala/fastjson v1.6.3
github.com/valyala/fastrand v1.1.0
github.com/valyala/fasttemplate v1.2.1
github.com/valyala/gozstd v1.17.0
github.com/valyala/quicktemplate v1.7.0
golang.org/x/net v0.0.0-20220630215102-69896b714898
golang.org/x/net v0.0.0-20220708220712-1185a9018129
golang.org/x/oauth2 v0.0.0-20220630143837-2104d58473e0
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e
golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d
google.golang.org/api v0.86.0
gopkg.in/yaml.v2 v2.4.0
)
@ -76,7 +76,7 @@ require (
golang.org/x/text v0.3.7 // indirect
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220630174209-ad1d48641aa7 // indirect
google.golang.org/genproto v0.0.0-20220711132622-b6f31b0ceb50 // indirect
google.golang.org/grpc v1.47.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
)

24
go.sum
View file

@ -146,8 +146,8 @@ github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQ
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
github.com/aws/aws-sdk-go v1.35.31/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.44.47 h1:uyiNvoR4wfZ8Bp4ghgbyzGFIg5knjZMUAd5S9ba9qNU=
github.com/aws/aws-sdk-go v1.44.47/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.51 h1:jO9hoLynZOrMM4dj0KjeKIK+c6PA+HQbKoHOkAEye2Y=
github.com/aws/aws-sdk-go v1.44.51/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@ -530,8 +530,8 @@ github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY=
github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI=
github.com/influxdata/influxdb v1.9.7 h1:asjvZJ8NFFmxkSw+kOJj1ItGLQdU1nvRQE3jvdQXeRU=
github.com/influxdata/influxdb v1.9.7/go.mod h1:YZMcI9MYeMGLcg7Td7z5YRk52tL85r5bF4qX6WCnSt4=
github.com/influxdata/influxdb v1.9.8 h1:wuw8ZwyIZgg/jn//9cwr4OpKIF5z9o83lIfpb19aO2Q=
github.com/influxdata/influxdb v1.9.8/go.mod h1:8Ft9mikW2GELpV154RV+F7ocPa5FS5G/rl4rH9INT/I=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk=
github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE=
@ -822,8 +822,8 @@ github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMW
github.com/uber/jaeger-lib v2.4.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.10.3 h1:oi571Fxz5aHugfBAJd5nkwSk3fzATXtMlpxdLylSCMo=
github.com/urfave/cli/v2 v2.10.3/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo=
github.com/urfave/cli/v2 v2.11.0 h1:c6bD90aLd2iEsokxhxkY5Er0zA2V9fId2aJfwmrF+do=
github.com/urfave/cli/v2 v2.11.0/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
@ -1002,8 +1002,8 @@ golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw=
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220708220712-1185a9018129 h1:vucSRfWwTsoXro7P+3Cjlr6flUMtzCwzlvkxEQtHHB0=
golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -1137,8 +1137,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e h1:CsOuNlbOuf0mzxJIefr6Q4uAUetRUwZE4qt7VfzP+xo=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d h1:/m5NbqQelATgoSPVC2Z23sR4kVNokFwDDyWh/3rGY+I=
golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1380,8 +1380,8 @@ google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljW
google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220630174209-ad1d48641aa7 h1:q4zUJDd0+knPFB9x20S3vnxzlYNBbt8Yd7zBMVMteeM=
google.golang.org/genproto v0.0.0-20220630174209-ad1d48641aa7/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220711132622-b6f31b0ceb50 h1:gyHXMCq6jOxTS4ywai4Ht8kjJcDRxkmtCJERlZ3nGms=
google.golang.org/genproto v0.0.0-20220711132622-b6f31b0ceb50/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=

View file

@ -17348,6 +17348,67 @@ var awsPartition = partition{
}: endpoint{},
},
},
"rolesanywhere": service{
Endpoints: serviceEndpoints{
endpointKey{
Region: "ap-east-1",
}: endpoint{},
endpointKey{
Region: "ap-northeast-1",
}: endpoint{},
endpointKey{
Region: "ap-northeast-2",
}: endpoint{},
endpointKey{
Region: "ap-northeast-3",
}: endpoint{},
endpointKey{
Region: "ap-south-1",
}: endpoint{},
endpointKey{
Region: "ap-southeast-1",
}: endpoint{},
endpointKey{
Region: "ap-southeast-2",
}: endpoint{},
endpointKey{
Region: "ca-central-1",
}: endpoint{},
endpointKey{
Region: "eu-central-1",
}: endpoint{},
endpointKey{
Region: "eu-north-1",
}: endpoint{},
endpointKey{
Region: "eu-west-1",
}: endpoint{},
endpointKey{
Region: "eu-west-2",
}: endpoint{},
endpointKey{
Region: "eu-west-3",
}: endpoint{},
endpointKey{
Region: "me-south-1",
}: endpoint{},
endpointKey{
Region: "sa-east-1",
}: endpoint{},
endpointKey{
Region: "us-east-1",
}: endpoint{},
endpointKey{
Region: "us-east-2",
}: endpoint{},
endpointKey{
Region: "us-west-1",
}: endpoint{},
endpointKey{
Region: "us-west-2",
}: endpoint{},
},
},
"route53": service{
PartitionEndpoint: "aws-global",
IsRegionalized: boxedFalse,

View file

@ -5,4 +5,4 @@ package aws
const SDKName = "aws-sdk-go"
// SDKVersion is the version of this SDK
const SDKVersion = "1.44.47"
const SDKVersion = "1.44.51"

View file

@ -7,6 +7,7 @@ import (
"io"
"os"
"path/filepath"
"reflect"
"sort"
"time"
)
@ -43,6 +44,9 @@ type App struct {
Version string
// Description of the program
Description string
// DefaultCommand is the (optional) name of a command
// to run if no command names are passed as CLI arguments.
DefaultCommand string
// List of commands to execute
Commands []*Command
// List of flags to parse
@ -333,13 +337,45 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
}
}
var c *Command
args := cCtx.Args()
if args.Present() {
name := args.First()
c := a.Command(name)
if c != nil {
return c.Run(cCtx)
if a.validCommandName(name) {
c = a.Command(name)
} else {
hasDefault := a.DefaultCommand != ""
isFlagName := checkStringSliceIncludes(name, cCtx.FlagNames())
var (
isDefaultSubcommand = false
defaultHasSubcommands = false
)
if hasDefault {
dc := a.Command(a.DefaultCommand)
defaultHasSubcommands = len(dc.Subcommands) > 0
for _, dcSub := range dc.Subcommands {
if checkStringSliceIncludes(name, dcSub.Names()) {
isDefaultSubcommand = true
break
}
}
}
if isFlagName || (hasDefault && (defaultHasSubcommands && isDefaultSubcommand)) {
argsWithDefault := a.argsWithDefaultCommand(args)
if !reflect.DeepEqual(args, argsWithDefault) {
c = a.Command(argsWithDefault.First())
}
}
}
} else if a.DefaultCommand != "" {
c = a.Command(a.DefaultCommand)
}
if c != nil {
return c.Run(cCtx)
}
if a.Action == nil {
@ -570,6 +606,41 @@ func (a *App) handleExitCoder(cCtx *Context, err error) {
}
}
func (a *App) commandNames() []string {
var cmdNames []string
for _, cmd := range a.Commands {
cmdNames = append(cmdNames, cmd.Names()...)
}
return cmdNames
}
func (a *App) validCommandName(checkCmdName string) bool {
valid := false
allCommandNames := a.commandNames()
for _, cmdName := range allCommandNames {
if checkCmdName == cmdName {
valid = true
break
}
}
return valid
}
func (a *App) argsWithDefaultCommand(oldArgs Args) Args {
if a.DefaultCommand != "" {
rawArgs := append([]string{a.DefaultCommand}, oldArgs.Slice()...)
newArgs := args(rawArgs)
return &newArgs
}
return oldArgs
}
// Author represents someone who has contributed to a cli project.
type Author struct {
Name string // The Authors name
@ -602,3 +673,15 @@ func HandleAction(action interface{}, cCtx *Context) (err error) {
return errInvalidActionType
}
func checkStringSliceIncludes(want string, sSlice []string) bool {
found := false
for _, s := range sSlice {
if want == s {
found = true
break
}
}
return found
}

View file

@ -43,6 +43,7 @@ flag_types:
value_pointer: true
struct_fields:
- { name: Layout, type: string }
- { name: Timezone, type: "*time.Location" }
# TODO: enable UintSlice
# UintSlice: {}

View file

@ -11,6 +11,7 @@ type Timestamp struct {
timestamp *time.Time
hasBeenSet bool
layout string
location *time.Location
}
// Timestamp constructor
@ -31,9 +32,22 @@ func (t *Timestamp) SetLayout(layout string) {
t.layout = layout
}
// Set perceived timezone of the to-be parsed time string
func (t *Timestamp) SetLocation(loc *time.Location) {
t.location = loc
}
// Parses the string value to timestamp
func (t *Timestamp) Set(value string) error {
timestamp, err := time.Parse(t.layout, value)
var timestamp time.Time
var err error
if t.location != nil {
timestamp, err = time.ParseInLocation(t.layout, value, t.location)
} else {
timestamp, err = time.Parse(t.layout, value)
}
if err != nil {
return err
}
@ -104,9 +118,11 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error {
f.Value = &Timestamp{}
}
f.Value.SetLayout(f.Layout)
f.Value.SetLocation(f.Timezone)
if f.Destination != nil {
f.Destination.SetLayout(f.Layout)
f.Destination.SetLocation(f.Timezone)
}
if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {

View file

@ -269,6 +269,9 @@ type App struct {
Version string
// Description of the program
Description string
// DefaultCommand is the (optional) name of a command
// to run if no command names are passed as CLI arguments.
DefaultCommand string
// List of commands to execute
Commands []*Command
// List of flags to parse
@ -1754,6 +1757,9 @@ func (t *Timestamp) Set(value string) error
func (t *Timestamp) SetLayout(layout string)
Set the timestamp string layout for future parsing
func (t *Timestamp) SetLocation(loc *time.Location)
Set perceived timezone of the to-be parsed time string
func (t *Timestamp) SetTimestamp(value time.Time)
Set the timestamp value directly
@ -1782,6 +1788,8 @@ type TimestampFlag struct {
EnvVars []string
Layout string
Timezone *time.Location
}
TimestampFlag is a flag with type *Timestamp

View file

@ -280,6 +280,8 @@ type TimestampFlag struct {
EnvVars []string
Layout string
Timezone *time.Location
}
// String returns a readable representation of this value (for usage defaults)

12
vendor/modules.txt vendored
View file

@ -34,7 +34,7 @@ github.com/VictoriaMetrics/metricsql/binaryop
# github.com/VividCortex/ewma v1.2.0
## explicit; go 1.12
github.com/VividCortex/ewma
# github.com/aws/aws-sdk-go v1.44.47
# github.com/aws/aws-sdk-go v1.44.51
## explicit; go 1.11
github.com/aws/aws-sdk-go/aws
github.com/aws/aws-sdk-go/aws/arn
@ -151,7 +151,7 @@ github.com/googleapis/gax-go/v2/internal
# github.com/googleapis/go-type-adapters v1.0.0
## explicit; go 1.11
github.com/googleapis/go-type-adapters/adapters
# github.com/influxdata/influxdb v1.9.7
# github.com/influxdata/influxdb v1.9.8
## explicit; go 1.17
github.com/influxdata/influxdb/client/v2
github.com/influxdata/influxdb/models
@ -229,7 +229,7 @@ github.com/rivo/uniseg
# github.com/russross/blackfriday/v2 v2.1.0
## explicit
github.com/russross/blackfriday/v2
# github.com/urfave/cli/v2 v2.10.3
# github.com/urfave/cli/v2 v2.11.0
## explicit; go 1.18
github.com/urfave/cli/v2
# github.com/valyala/bytebufferpool v1.0.0
@ -281,7 +281,7 @@ go.opencensus.io/trace/tracestate
go.uber.org/atomic
# go.uber.org/goleak v1.1.11-0.20210813005559-691160354723
## explicit; go 1.13
# golang.org/x/net v0.0.0-20220630215102-69896b714898
# golang.org/x/net v0.0.0-20220708220712-1185a9018129
## explicit; go 1.17
golang.org/x/net/context
golang.org/x/net/context/ctxhttp
@ -306,7 +306,7 @@ golang.org/x/oauth2/jwt
# golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
## explicit
golang.org/x/sync/errgroup
# golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e
# golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d
## explicit; go 1.17
golang.org/x/sys/internal/unsafeheader
golang.org/x/sys/unix
@ -354,7 +354,7 @@ google.golang.org/appengine/internal/socket
google.golang.org/appengine/internal/urlfetch
google.golang.org/appengine/socket
google.golang.org/appengine/urlfetch
# google.golang.org/genproto v0.0.0-20220630174209-ad1d48641aa7
# google.golang.org/genproto v0.0.0-20220711132622-b6f31b0ceb50
## explicit; go 1.15
google.golang.org/genproto/googleapis/api/annotations
google.golang.org/genproto/googleapis/iam/v1