mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-15 08:23:34 +01:00
app/vmselect: add /select/0/prometheus/expand-with-exprs page
This commit is contained in:
parent
dafa819c54
commit
6d13ca2b88
@ -305,7 +305,7 @@ func selectHandler(qt *querytracer.Tracer, startTime time.Time, w http.ResponseW
|
|||||||
fmt.Fprintf(w, "See <a href='https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#url-format'>docs</a></br>")
|
fmt.Fprintf(w, "See <a href='https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#url-format'>docs</a></br>")
|
||||||
fmt.Fprintf(w, "Useful endpoints:</br>")
|
fmt.Fprintf(w, "Useful endpoints:</br>")
|
||||||
fmt.Fprintf(w, `<a href="vmui">Web UI</a><br>`)
|
fmt.Fprintf(w, `<a href="vmui">Web UI</a><br>`)
|
||||||
fmt.Fprintf(w, `<a href="metric-relabel-debug">Metric-level relabel debugging</a></br>`)
|
fmt.Fprintf(w, `<a href="prometheus/metric-relabel-debug">Metric-level relabel debugging</a></br>`)
|
||||||
fmt.Fprintf(w, `<a href="prometheus/api/v1/status/tsdb">tsdb status page</a><br>`)
|
fmt.Fprintf(w, `<a href="prometheus/api/v1/status/tsdb">tsdb status page</a><br>`)
|
||||||
fmt.Fprintf(w, `<a href="prometheus/api/v1/status/top_queries">top queries</a><br>`)
|
fmt.Fprintf(w, `<a href="prometheus/api/v1/status/top_queries">top queries</a><br>`)
|
||||||
fmt.Fprintf(w, `<a href="prometheus/api/v1/status/active_queries">active queries</a><br>`)
|
fmt.Fprintf(w, `<a href="prometheus/api/v1/status/active_queries">active queries</a><br>`)
|
||||||
@ -594,6 +594,10 @@ func selectHandler(qt *querytracer.Tracer, startTime time.Time, w http.ResponseW
|
|||||||
promscrapeTargetRelabelDebugRequests.Inc()
|
promscrapeTargetRelabelDebugRequests.Inc()
|
||||||
promscrape.WriteTargetRelabelDebug(w, r)
|
promscrape.WriteTargetRelabelDebug(w, r)
|
||||||
return true
|
return true
|
||||||
|
case "prometheus/expand-with-exprs", "expand-with-exprs":
|
||||||
|
expandWithExprsRequests.Inc()
|
||||||
|
prometheus.ExpandWithExprs(w, r)
|
||||||
|
return true
|
||||||
case "prometheus/api/v1/rules", "prometheus/rules":
|
case "prometheus/api/v1/rules", "prometheus/rules":
|
||||||
rulesRequests.Inc()
|
rulesRequests.Inc()
|
||||||
if len(*vmalertProxyURL) > 0 {
|
if len(*vmalertProxyURL) > 0 {
|
||||||
@ -768,6 +772,8 @@ var (
|
|||||||
promscrapeMetricRelabelDebugRequests = metrics.NewCounter(`vm_http_requests_total{path="/select/{}/prometheus/metric-relabel-debug"}`)
|
promscrapeMetricRelabelDebugRequests = metrics.NewCounter(`vm_http_requests_total{path="/select/{}/prometheus/metric-relabel-debug"}`)
|
||||||
promscrapeTargetRelabelDebugRequests = metrics.NewCounter(`vm_http_requests_total{path="/select/{}/prometheus/target-relabel-debug"}`)
|
promscrapeTargetRelabelDebugRequests = metrics.NewCounter(`vm_http_requests_total{path="/select/{}/prometheus/target-relabel-debug"}`)
|
||||||
|
|
||||||
|
expandWithExprsRequests = metrics.NewCounter(`vm_http_requests_total{path="/select/{}/prometheus/expand-with-exprs"}`)
|
||||||
|
|
||||||
vmalertRequests = metrics.NewCounter(`vm_http_requests_total{path="/select/{}/prometheus/vmalert"}`)
|
vmalertRequests = metrics.NewCounter(`vm_http_requests_total{path="/select/{}/prometheus/vmalert"}`)
|
||||||
rulesRequests = metrics.NewCounter(`vm_http_requests_total{path="/select/{}/prometheus/api/v1/rules"}`)
|
rulesRequests = metrics.NewCounter(`vm_http_requests_total{path="/select/{}/prometheus/api/v1/rules"}`)
|
||||||
alertsRequests = metrics.NewCounter(`vm_http_requests_total{path="/select/{}/prometheus/api/v1/alerts"}`)
|
alertsRequests = metrics.NewCounter(`vm_http_requests_total{path="/select/{}/prometheus/api/v1/alerts"}`)
|
||||||
|
245
app/vmselect/prometheus/expand-with-exprs.qtpl
Normal file
245
app/vmselect/prometheus/expand-with-exprs.qtpl
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
{% import (
|
||||||
|
"github.com/VictoriaMetrics/metricsql"
|
||||||
|
) %}
|
||||||
|
|
||||||
|
{% stripspace %}
|
||||||
|
|
||||||
|
// ExpandWithExprsResponse returns a webpage, which expands with templates in q MetricsQL.
|
||||||
|
{% func ExpandWithExprsResponse(q string) %}
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Expand WITH expressions</title>
|
||||||
|
<style>
|
||||||
|
p { font-weight: bold }
|
||||||
|
textarea { margin: 1em }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<form method="get">
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<a href="https://docs.victoriametrics.com/MetricsQL.html">MetricsQL</a> query with optional WITH expressions:
|
||||||
|
</p>
|
||||||
|
<textarea name="query" style="height: 15em; width: 90%">{%s q %}</textarea><br/>
|
||||||
|
<input type="submit" value="Expand" />
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="https://docs.victoriametrics.com/MetricsQL.html">MetricsQL</a> query after expanding WITH expressions and applying other optimizations:
|
||||||
|
</p>
|
||||||
|
<textarea style="height: 5em; width: 90%" readonly="readonly">{%= expandWithExprs(q) %}</textarea>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>{%= withExprsTutorial() %}</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
{% endfunc %}
|
||||||
|
|
||||||
|
{% func expandWithExprs(q string) %}
|
||||||
|
{% if len(q) == 0 %}
|
||||||
|
{% return %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% code expr, err := metricsql.Parse(q) %}
|
||||||
|
{% if err != nil %}
|
||||||
|
Cannot parse query: {%v err %}
|
||||||
|
{% else %}
|
||||||
|
{% code expr = metricsql.Optimize(expr) %}
|
||||||
|
{%z expr.AppendString(nil) %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfunc %}
|
||||||
|
|
||||||
|
{% endstripspace %}
|
||||||
|
|
||||||
|
{% func withExprsTutorial() %}
|
||||||
|
<h3>Tutorial for WITH expressions in <a href="https://docs.victoriametrics.com/MetricsQL.html">MetricsQL</a></h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Let's look at the following real query from <a href="https://grafana.com/dashboards/1860">Node Exporter Full</a> dashboard:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
(
|
||||||
|
(
|
||||||
|
node_memory_MemTotal_bytes{instance=~"$node:$port", job=~"$job"}
|
||||||
|
-
|
||||||
|
node_memory_MemFree_bytes{instance=~"$node:$port", job=~"$job"}
|
||||||
|
)
|
||||||
|
/
|
||||||
|
node_memory_MemTotal_bytes{instance=~"$node:$port", job=~"$job"}
|
||||||
|
)
|
||||||
|
*
|
||||||
|
100
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
It is clear the query calculates the percentage of used memory
|
||||||
|
for the given $node, $port and $job. Isn't it? :)
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
What's wrong with this query? Copy-pasted label filters for distinct timeseries
|
||||||
|
which makes it easy to mistype these filters during modification.
|
||||||
|
Let's simplify the query with WITH expressions:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
WITH (
|
||||||
|
commonFilters = {instance=~"$node:$port",job=~"$job"}
|
||||||
|
)
|
||||||
|
(
|
||||||
|
node_memory_MemTotal_bytes{commonFilters}
|
||||||
|
-
|
||||||
|
node_memory_MemFree_bytes{commonFilters}
|
||||||
|
)
|
||||||
|
/
|
||||||
|
node_memory_MemTotal_bytes{commonFilters} * 100
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now label filters are located in a single place instead of three distinct places.
|
||||||
|
The query mentions node_memory_MemTotal_bytes metric twice and {commonFilters}
|
||||||
|
three times. WITH expressions may improve this:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
WITH (
|
||||||
|
my_resource_utilization(free, limit, filters) = (limit{filters} - free{filters}) / limit{filters} * 100
|
||||||
|
)
|
||||||
|
my_resource_utilization(
|
||||||
|
node_memory_MemFree_bytes,
|
||||||
|
node_memory_MemTotal_bytes,
|
||||||
|
{instance=~"$node:$port",job=~"$job"},
|
||||||
|
)
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now the template function my_resource_utilization() may be used for monitoring arbitrary
|
||||||
|
resources - memory, CPU, network, storage, you name it.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Let's take another nice query from <a href="https://grafana.com/dashboards/1860">Node Exporter Full</a> dashboard:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
count(
|
||||||
|
count(node_cpu_seconds_total{instance=~"$node:$port",job=~"$job"}) by (cpu)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
-
|
||||||
|
avg(
|
||||||
|
sum by (mode) (rate(node_cpu_seconds_total{mode='idle',instance=~"$node:$port",job=~"$job"}[5m]))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
*
|
||||||
|
100
|
||||||
|
)
|
||||||
|
/
|
||||||
|
count(
|
||||||
|
count(node_cpu_seconds_total{instance=~"$node:$port",job=~"$job"}) by (cpu)
|
||||||
|
)
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Do you understand what does this mess do? Is it manageable? :) WITH expressions are happy to help in a few iterations.
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
1. Extract common filters used in multiple places into a commonFilters variable:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
WITH (
|
||||||
|
commonFilters = {instance=~"$node:$port",job=~"$job"}
|
||||||
|
)
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
count(
|
||||||
|
count(node_cpu_seconds_total{commonFilters}) by (cpu)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
-
|
||||||
|
avg(
|
||||||
|
sum by (mode) (rate(node_cpu_seconds_total{mode='idle',commonFilters}[5m]))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
*
|
||||||
|
100
|
||||||
|
)
|
||||||
|
/
|
||||||
|
count(
|
||||||
|
count(node_cpu_seconds_total{commonFilters}) by (cpu)
|
||||||
|
)
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
2. Extract "count(count(...) by (cpu))" into cpuCount variable:
|
||||||
|
</p>
|
||||||
|
<pre>
|
||||||
|
WITH (
|
||||||
|
commonFilters = {instance=~"$node:$port",job=~"$job"},
|
||||||
|
cpuCount = count(count(node_cpu_seconds_total{commonFilters}) by (cpu))
|
||||||
|
)
|
||||||
|
(
|
||||||
|
(
|
||||||
|
cpuCount
|
||||||
|
-
|
||||||
|
avg(
|
||||||
|
sum by (mode) (rate(node_cpu_seconds_total{mode='idle',commonFilters}[5m]))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
*
|
||||||
|
100
|
||||||
|
) / cpuCount
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
3. Extract rate(...) part into cpuIdle variable, since it is clear now that this part calculates the number of idle CPUs:
|
||||||
|
</p>
|
||||||
|
<pre>
|
||||||
|
WITH (
|
||||||
|
commonFilters = {instance=~"$node:$port",job=~"$job"},
|
||||||
|
cpuCount = count(count(node_cpu_seconds_total{commonFilters}) by (cpu)),
|
||||||
|
cpuIdle = sum(rate(node_cpu_seconds_total{mode='idle',commonFilters}[5m]))
|
||||||
|
)
|
||||||
|
((cpuCount - cpuIdle) * 100) / cpuCount
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
4. Put node_cpu_seconds_total{commonFilters} into its own varialbe with the name cpuSeconds:
|
||||||
|
</p>
|
||||||
|
<pre>
|
||||||
|
WITH (
|
||||||
|
cpuSeconds = node_cpu_seconds_total{instance=~"$node:$port",job=~"$job"},
|
||||||
|
cpuCount = count(count(cpuSeconds) by (cpu)),
|
||||||
|
cpuIdle = sum(rate(cpuSeconds{mode='idle'}[5m]))
|
||||||
|
)
|
||||||
|
((cpuCount - cpuIdle) * 100) / cpuCount
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now the query became more clear comparing to the initial query.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
WITH expressions may be nested and may be put anywhere. Try expanding the following query:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
WITH (
|
||||||
|
f(a, b) = WITH (
|
||||||
|
f1(x) = b-x,
|
||||||
|
f2(x) = x+x
|
||||||
|
) f1(a)*f2(b)
|
||||||
|
) f(foo, with(x=bar) x)
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
{% endfunc %}
|
345
app/vmselect/prometheus/expand-with-exprs.qtpl.go
Normal file
345
app/vmselect/prometheus/expand-with-exprs.qtpl.go
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
// Code generated by qtc from "expand-with-exprs.qtpl". DO NOT EDIT.
|
||||||
|
// See https://github.com/valyala/quicktemplate for details.
|
||||||
|
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:1
|
||||||
|
package prometheus
|
||||||
|
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:1
|
||||||
|
import (
|
||||||
|
"github.com/VictoriaMetrics/metricsql"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExpandWithExprsResponse returns a webpage, which expands with templates in q MetricsQL.
|
||||||
|
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:8
|
||||||
|
import (
|
||||||
|
qtio422016 "io"
|
||||||
|
|
||||||
|
qt422016 "github.com/valyala/quicktemplate"
|
||||||
|
)
|
||||||
|
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:8
|
||||||
|
var (
|
||||||
|
_ = qtio422016.Copy
|
||||||
|
_ = qt422016.AcquireByteBuffer
|
||||||
|
)
|
||||||
|
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:8
|
||||||
|
func StreamExpandWithExprsResponse(qw422016 *qt422016.Writer, q string) {
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:8
|
||||||
|
qw422016.N().S(`<html><head><title>Expand WITH expressions</title><style>p { font-weight: bold }textarea { margin: 1em }</style></head><body><div><form method="get"><div><p><a href="https://docs.victoriametrics.com/MetricsQL.html">MetricsQL</a> query with optional WITH expressions:</p><textarea name="query" style="height: 15em; width: 90%">`)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:25
|
||||||
|
qw422016.E().S(q)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:25
|
||||||
|
qw422016.N().S(`</textarea><br/><input type="submit" value="Expand" /><p><a href="https://docs.victoriametrics.com/MetricsQL.html">MetricsQL</a> query after expanding WITH expressions and applying other optimizations:</p><textarea style="height: 5em; width: 90%" readonly="readonly">`)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:31
|
||||||
|
streamexpandWithExprs(qw422016, q)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:31
|
||||||
|
qw422016.N().S(`</textarea></div></form></div><div>`)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:36
|
||||||
|
streamwithExprsTutorial(qw422016)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:36
|
||||||
|
qw422016.N().S(`</div></body></html>`)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||||
|
}
|
||||||
|
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||||
|
func WriteExpandWithExprsResponse(qq422016 qtio422016.Writer, q string) {
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||||
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||||
|
StreamExpandWithExprsResponse(qw422016, q)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||||
|
qt422016.ReleaseWriter(qw422016)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||||
|
}
|
||||||
|
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||||
|
func ExpandWithExprsResponse(q string) string {
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||||
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||||
|
WriteExpandWithExprsResponse(qb422016, q)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||||
|
qs422016 := string(qb422016.B)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||||
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||||
|
return qs422016
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||||
|
}
|
||||||
|
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:42
|
||||||
|
func streamexpandWithExprs(qw422016 *qt422016.Writer, q string) {
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:43
|
||||||
|
if len(q) == 0 {
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:44
|
||||||
|
return
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:45
|
||||||
|
}
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:47
|
||||||
|
expr, err := metricsql.Parse(q)
|
||||||
|
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:48
|
||||||
|
if err != nil {
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:48
|
||||||
|
qw422016.N().S(`Cannot parse query:`)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:49
|
||||||
|
qw422016.E().V(err)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:50
|
||||||
|
} else {
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:51
|
||||||
|
expr = metricsql.Optimize(expr)
|
||||||
|
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:52
|
||||||
|
qw422016.E().Z(expr.AppendString(nil))
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:53
|
||||||
|
}
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||||
|
}
|
||||||
|
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||||
|
func writeexpandWithExprs(qq422016 qtio422016.Writer, q string) {
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||||
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||||
|
streamexpandWithExprs(qw422016, q)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||||
|
qt422016.ReleaseWriter(qw422016)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||||
|
}
|
||||||
|
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||||
|
func expandWithExprs(q string) string {
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||||
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||||
|
writeexpandWithExprs(qb422016, q)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||||
|
qs422016 := string(qb422016.B)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||||
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||||
|
return qs422016
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||||
|
}
|
||||||
|
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:58
|
||||||
|
func streamwithExprsTutorial(qw422016 *qt422016.Writer) {
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:58
|
||||||
|
qw422016.N().S(`
|
||||||
|
<h3>Tutorial for WITH expressions in <a href="https://docs.victoriametrics.com/MetricsQL.html">MetricsQL</a></h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Let's look at the following real query from <a href="https://grafana.com/dashboards/1860">Node Exporter Full</a> dashboard:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
(
|
||||||
|
(
|
||||||
|
node_memory_MemTotal_bytes{instance=~"$node:$port", job=~"$job"}
|
||||||
|
-
|
||||||
|
node_memory_MemFree_bytes{instance=~"$node:$port", job=~"$job"}
|
||||||
|
)
|
||||||
|
/
|
||||||
|
node_memory_MemTotal_bytes{instance=~"$node:$port", job=~"$job"}
|
||||||
|
)
|
||||||
|
*
|
||||||
|
100
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
It is clear the query calculates the percentage of used memory
|
||||||
|
for the given $node, $port and $job. Isn't it? :)
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
What's wrong with this query? Copy-pasted label filters for distinct timeseries
|
||||||
|
which makes it easy to mistype these filters during modification.
|
||||||
|
Let's simplify the query with WITH expressions:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
WITH (
|
||||||
|
commonFilters = {instance=~"$node:$port",job=~"$job"}
|
||||||
|
)
|
||||||
|
(
|
||||||
|
node_memory_MemTotal_bytes{commonFilters}
|
||||||
|
-
|
||||||
|
node_memory_MemFree_bytes{commonFilters}
|
||||||
|
)
|
||||||
|
/
|
||||||
|
node_memory_MemTotal_bytes{commonFilters} * 100
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now label filters are located in a single place instead of three distinct places.
|
||||||
|
The query mentions node_memory_MemTotal_bytes metric twice and {commonFilters}
|
||||||
|
three times. WITH expressions may improve this:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
WITH (
|
||||||
|
my_resource_utilization(free, limit, filters) = (limit{filters} - free{filters}) / limit{filters} * 100
|
||||||
|
)
|
||||||
|
my_resource_utilization(
|
||||||
|
node_memory_MemFree_bytes,
|
||||||
|
node_memory_MemTotal_bytes,
|
||||||
|
{instance=~"$node:$port",job=~"$job"},
|
||||||
|
)
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now the template function my_resource_utilization() may be used for monitoring arbitrary
|
||||||
|
resources - memory, CPU, network, storage, you name it.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Let's take another nice query from <a href="https://grafana.com/dashboards/1860">Node Exporter Full</a> dashboard:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
count(
|
||||||
|
count(node_cpu_seconds_total{instance=~"$node:$port",job=~"$job"}) by (cpu)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
-
|
||||||
|
avg(
|
||||||
|
sum by (mode) (rate(node_cpu_seconds_total{mode='idle',instance=~"$node:$port",job=~"$job"}[5m]))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
*
|
||||||
|
100
|
||||||
|
)
|
||||||
|
/
|
||||||
|
count(
|
||||||
|
count(node_cpu_seconds_total{instance=~"$node:$port",job=~"$job"}) by (cpu)
|
||||||
|
)
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Do you understand what does this mess do? Is it manageable? :) WITH expressions are happy to help in a few iterations.
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
1. Extract common filters used in multiple places into a commonFilters variable:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
WITH (
|
||||||
|
commonFilters = {instance=~"$node:$port",job=~"$job"}
|
||||||
|
)
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
count(
|
||||||
|
count(node_cpu_seconds_total{commonFilters}) by (cpu)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
-
|
||||||
|
avg(
|
||||||
|
sum by (mode) (rate(node_cpu_seconds_total{mode='idle',commonFilters}[5m]))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
*
|
||||||
|
100
|
||||||
|
)
|
||||||
|
/
|
||||||
|
count(
|
||||||
|
count(node_cpu_seconds_total{commonFilters}) by (cpu)
|
||||||
|
)
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
2. Extract "count(count(...) by (cpu))" into cpuCount variable:
|
||||||
|
</p>
|
||||||
|
<pre>
|
||||||
|
WITH (
|
||||||
|
commonFilters = {instance=~"$node:$port",job=~"$job"},
|
||||||
|
cpuCount = count(count(node_cpu_seconds_total{commonFilters}) by (cpu))
|
||||||
|
)
|
||||||
|
(
|
||||||
|
(
|
||||||
|
cpuCount
|
||||||
|
-
|
||||||
|
avg(
|
||||||
|
sum by (mode) (rate(node_cpu_seconds_total{mode='idle',commonFilters}[5m]))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
*
|
||||||
|
100
|
||||||
|
) / cpuCount
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
3. Extract rate(...) part into cpuIdle variable, since it is clear now that this part calculates the number of idle CPUs:
|
||||||
|
</p>
|
||||||
|
<pre>
|
||||||
|
WITH (
|
||||||
|
commonFilters = {instance=~"$node:$port",job=~"$job"},
|
||||||
|
cpuCount = count(count(node_cpu_seconds_total{commonFilters}) by (cpu)),
|
||||||
|
cpuIdle = sum(rate(node_cpu_seconds_total{mode='idle',commonFilters}[5m]))
|
||||||
|
)
|
||||||
|
((cpuCount - cpuIdle) * 100) / cpuCount
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
4. Put node_cpu_seconds_total{commonFilters} into its own varialbe with the name cpuSeconds:
|
||||||
|
</p>
|
||||||
|
<pre>
|
||||||
|
WITH (
|
||||||
|
cpuSeconds = node_cpu_seconds_total{instance=~"$node:$port",job=~"$job"},
|
||||||
|
cpuCount = count(count(cpuSeconds) by (cpu)),
|
||||||
|
cpuIdle = sum(rate(cpuSeconds{mode='idle'}[5m]))
|
||||||
|
)
|
||||||
|
((cpuCount - cpuIdle) * 100) / cpuCount
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now the query became more clear comparing to the initial query.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
WITH expressions may be nested and may be put anywhere. Try expanding the following query:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
WITH (
|
||||||
|
f(a, b) = WITH (
|
||||||
|
f1(x) = b-x,
|
||||||
|
f2(x) = x+x
|
||||||
|
) f1(a)*f2(b)
|
||||||
|
) f(foo, with(x=bar) x)
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
`)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||||
|
}
|
||||||
|
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||||
|
func writewithExprsTutorial(qq422016 qtio422016.Writer) {
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||||
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||||
|
streamwithExprsTutorial(qw422016)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||||
|
qt422016.ReleaseWriter(qw422016)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||||
|
}
|
||||||
|
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||||
|
func withExprsTutorial() string {
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||||
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||||
|
writewithExprsTutorial(qb422016)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||||
|
qs422016 := string(qb422016.B)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||||
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||||
|
return qs422016
|
||||||
|
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||||
|
}
|
@ -61,6 +61,15 @@ var (
|
|||||||
// Default step used if not set.
|
// Default step used if not set.
|
||||||
const defaultStep = 5 * 60 * 1000
|
const defaultStep = 5 * 60 * 1000
|
||||||
|
|
||||||
|
// ExpandWithExprs handles the request to /expand-with-exprs
|
||||||
|
func ExpandWithExprs(w http.ResponseWriter, r *http.Request) {
|
||||||
|
query := r.FormValue("query")
|
||||||
|
bw := bufferedwriter.Get(w)
|
||||||
|
defer bufferedwriter.Put(bw)
|
||||||
|
WriteExpandWithExprsResponse(bw, query)
|
||||||
|
_ = bw.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
// FederateHandler implements /federate . See https://prometheus.io/docs/prometheus/latest/federation/
|
// FederateHandler implements /federate . See https://prometheus.io/docs/prometheus/latest/federation/
|
||||||
func FederateHandler(startTime time.Time, at *auth.Token, w http.ResponseWriter, r *http.Request) error {
|
func FederateHandler(startTime time.Time, at *auth.Token, w http.ResponseWriter, r *http.Request) error {
|
||||||
defer federateDuration.UpdateDuration(startTime)
|
defer federateDuration.UpdateDuration(startTime)
|
||||||
@ -413,10 +422,7 @@ func exportHandler(qt *querytracer.Tracer, at *auth.Token, w http.ResponseWriter
|
|||||||
if format == "promapi" {
|
if format == "promapi" {
|
||||||
WriteExportPromAPIFooter(bw, qt)
|
WriteExportPromAPIFooter(bw, qt)
|
||||||
}
|
}
|
||||||
if err := bw.Flush(); err != nil {
|
return bw.Flush()
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type exportBlock struct {
|
type exportBlock struct {
|
||||||
|
@ -673,7 +673,7 @@ Released at 12-04-2022
|
|||||||
* BUGFIX: [VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): properly propagate limits at `-search.max*` command-line flags from `vminsert` to `vmstorage`. The limits are `-search.maxUniqueTimeseries`, `-search.maxSeries`, `-search.maxFederateSeries`, `-search.maxExportSeries`, `-search.maxGraphiteSeries` and `-search.maxTSDBStatusSeries`. They weren't propagated to `vmstorage` because of the bug. These limits were introduced in [v1.76.0](https://docs.victoriametrics.com/CHANGELOG.html#v1760). See [this bug](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2450).
|
* BUGFIX: [VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): properly propagate limits at `-search.max*` command-line flags from `vminsert` to `vmstorage`. The limits are `-search.maxUniqueTimeseries`, `-search.maxSeries`, `-search.maxFederateSeries`, `-search.maxExportSeries`, `-search.maxGraphiteSeries` and `-search.maxTSDBStatusSeries`. They weren't propagated to `vmstorage` because of the bug. These limits were introduced in [v1.76.0](https://docs.victoriametrics.com/CHANGELOG.html#v1760). See [this bug](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2450).
|
||||||
* BUGFIX: fix goroutine leak and possible deadlock when importing invalid data via [native binary format](https://docs.victoriametrics.com/#how-to-import-data-in-native-format). See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/2423).
|
* BUGFIX: fix goroutine leak and possible deadlock when importing invalid data via [native binary format](https://docs.victoriametrics.com/#how-to-import-data-in-native-format). See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/2423).
|
||||||
* BUGFIX: [Graphite Render API](https://docs.victoriametrics.com/#graphite-render-api-usage): properly calculate [hitCount](https://graphite.readthedocs.io/en/latest/functions.html#graphite.render.functions.hitcount) function. Previously it could return empty results if there were no original samples in some parts of the selected time range.
|
* BUGFIX: [Graphite Render API](https://docs.victoriametrics.com/#graphite-render-api-usage): properly calculate [hitCount](https://graphite.readthedocs.io/en/latest/functions.html#graphite.render.functions.hitcount) function. Previously it could return empty results if there were no original samples in some parts of the selected time range.
|
||||||
* BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): allow overriding built-in function names inside [WITH templates](https://play.victoriametrics.com/promql/expand-with-exprs). For example, `WITH (sum(a,b) = a + b + 1) sum(x,y)` now expands into `x + y + 1`. Previously such a query would fail with `cannot use reserved name` error. See [this bugreport](https://github.com/VictoriaMetrics/metricsql/issues/5).
|
* BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): allow overriding built-in function names inside [WITH templates](https://play.victoriametrics.com/select/accounting/1/6a716b0f-38bc-4856-90ce-448fd713e3fe/expand-with-exprs). For example, `WITH (sum(a,b) = a + b + 1) sum(x,y)` now expands into `x + y + 1`. Previously such a query would fail with `cannot use reserved name` error. See [this bugreport](https://github.com/VictoriaMetrics/metricsql/issues/5).
|
||||||
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): properly display values greater than 1000 on Y axis. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2409).
|
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): properly display values greater than 1000 on Y axis. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2409).
|
||||||
|
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ The list of MetricsQL features:
|
|||||||
* `if` binary operator. `q1 if q2` removes values from `q1` for missing values from `q2`.
|
* `if` binary operator. `q1 if q2` removes values from `q1` for missing values from `q2`.
|
||||||
* `ifnot` binary operator. `q1 ifnot q2` removes values from `q1` for existing values from `q2`.
|
* `ifnot` binary operator. `q1 ifnot q2` removes values from `q1` for existing values from `q2`.
|
||||||
* `WITH` templates. This feature simplifies writing and managing complex queries.
|
* `WITH` templates. This feature simplifies writing and managing complex queries.
|
||||||
Go to [WITH templates playground](https://play.victoriametrics.com/promql/expand-with-exprs) and try it.
|
Go to [WITH templates playground](https://play.victoriametrics.com/select/accounting/1/6a716b0f-38bc-4856-90ce-448fd713e3fe/expand-with-exprs) and try it.
|
||||||
* String literals may be concatenated. This is useful with `WITH` templates:
|
* String literals may be concatenated. This is useful with `WITH` templates:
|
||||||
`WITH (commonPrefix="long_metric_prefix_") {__name__=commonPrefix+"suffix1"} / {__name__=commonPrefix+"suffix2"}`.
|
`WITH (commonPrefix="long_metric_prefix_") {__name__=commonPrefix+"suffix1"} / {__name__=commonPrefix+"suffix2"}`.
|
||||||
* `keep_metric_names` modifier can be applied to all the [rollup functions](#rollup-functions) and [transform functions](#transform-functions).
|
* `keep_metric_names` modifier can be applied to all the [rollup functions](#rollup-functions) and [transform functions](#transform-functions).
|
||||||
|
Loading…
Reference in New Issue
Block a user