mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2025-01-20 07:19:17 +01:00
vmui: Integrate WITH template playground (#3831)
* feat: add WithTemplate page * app/vmselect/prometheus: enable json mode for expand with expr API * app/vmselect/prometheus: enable CORS and add content type * feat: add api for expand with templates * fix: remove console from useExpandWithExprs * app/vmselect/prometheus: fix escaping * vmui: integrate WITH template * app/vmctl: check content type instead of form param * fix: add content-type for fetch with-exprs * fix: add a header to the server's response that allows the "Content-Type" header * app/vmctl: added comment and cleanup * app/vmctl: use format query param --------- Co-authored-by: dmitryk-dk <kozlovdmitriyy@gmail.com>
This commit is contained in:
parent
e2053baf32
commit
68e31a6000
@ -469,6 +469,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
|
||||
return true
|
||||
case "/expand-with-exprs":
|
||||
expandWithExprsRequests.Inc()
|
||||
httpserver.EnableCORS(w, r)
|
||||
prometheus.ExpandWithExprs(w, r)
|
||||
return true
|
||||
case "/api/v1/rules", "/rules":
|
||||
|
@ -1,4 +1,5 @@
|
||||
{% import (
|
||||
"fmt"
|
||||
"github.com/VictoriaMetrics/metricsql"
|
||||
) %}
|
||||
|
||||
@ -243,3 +244,31 @@ WITH (
|
||||
</pre>
|
||||
|
||||
{% endfunc %}
|
||||
|
||||
{% stripspace %}
|
||||
{% func ExpandWithExprsJSONResponse(q string) %}
|
||||
{%= expandWithExprsJSON(q) %}
|
||||
{% endfunc %}
|
||||
|
||||
{% func expandWithExprsJSON(q string) %}
|
||||
{% if len(q) == 0 %}
|
||||
{
|
||||
"status": "error",
|
||||
"error": "query string cannot be empty"
|
||||
}
|
||||
{% return %}
|
||||
{% endif %}
|
||||
|
||||
{
|
||||
{% code expr, err := metricsql.Parse(q) %}
|
||||
{% if err != nil %}
|
||||
"status": "error",
|
||||
"error": {%q= fmt.Sprintf("Cannot parse query: %s", err) %}
|
||||
{% else %}
|
||||
{% code expr = metricsql.Optimize(expr) %}
|
||||
"status": "success",
|
||||
"expr": {%q= string(expr.AppendString(nil)) %}
|
||||
{% endif %}
|
||||
}
|
||||
{% endfunc %}
|
||||
{% endstripspace %}
|
||||
|
@ -6,127 +6,128 @@ package prometheus
|
||||
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:1
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/VictoriaMetrics/metricsql"
|
||||
)
|
||||
|
||||
// ExpandWithExprsResponse returns a webpage, which expands with templates in q MetricsQL.
|
||||
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:8
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:9
|
||||
import (
|
||||
qtio422016 "io"
|
||||
|
||||
qt422016 "github.com/valyala/quicktemplate"
|
||||
)
|
||||
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:8
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:9
|
||||
var (
|
||||
_ = qtio422016.Copy
|
||||
_ = qt422016.AcquireByteBuffer
|
||||
)
|
||||
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:8
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:9
|
||||
func StreamExpandWithExprsResponse(qw422016 *qt422016.Writer, q string) {
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:8
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:9
|
||||
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
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:26
|
||||
qw422016.E().S(q)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:25
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:26
|
||||
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
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:32
|
||||
streamexpandWithExprs(qw422016, q)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:31
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:32
|
||||
qw422016.N().S(`</textarea></div></form></div><div>`)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:36
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:37
|
||||
streamwithExprsTutorial(qw422016)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:36
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:37
|
||||
qw422016.N().S(`</div></body></html>`)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:41
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:41
|
||||
func WriteExpandWithExprsResponse(qq422016 qtio422016.Writer, q string) {
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:41
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:41
|
||||
StreamExpandWithExprsResponse(qw422016, q)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:41
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:41
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:41
|
||||
func ExpandWithExprsResponse(q string) string {
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:41
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:41
|
||||
WriteExpandWithExprsResponse(qb422016, q)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:41
|
||||
qs422016 := string(qb422016.B)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:41
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:41
|
||||
return qs422016
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:40
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:41
|
||||
}
|
||||
|
||||
//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 {
|
||||
func streamexpandWithExprs(qw422016 *qt422016.Writer, q string) {
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:44
|
||||
return
|
||||
if len(q) == 0 {
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:45
|
||||
return
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:46
|
||||
}
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:47
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:48
|
||||
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)
|
||||
if err != nil {
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:49
|
||||
qw422016.N().S(`Cannot parse query:`)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:50
|
||||
} else {
|
||||
qw422016.E().V(err)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:51
|
||||
} else {
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:52
|
||||
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
|
||||
qw422016.E().Z(expr.AppendString(nil))
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||
}
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:55
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:55
|
||||
func writeexpandWithExprs(qq422016 qtio422016.Writer, q string) {
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:55
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:55
|
||||
streamexpandWithExprs(qw422016, q)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:55
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:55
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:55
|
||||
func expandWithExprs(q string) string {
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:55
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:55
|
||||
writeexpandWithExprs(qb422016, q)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:55
|
||||
qs422016 := string(qb422016.B)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:55
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:55
|
||||
return qs422016
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:54
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:55
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:58
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:59
|
||||
func streamwithExprsTutorial(qw422016 *qt422016.Writer) {
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:58
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:59
|
||||
qw422016.N().S(`
|
||||
<h3>Tutorial for WITH expressions in <a href="https://docs.victoriametrics.com/MetricsQL.html">MetricsQL</a></h3>
|
||||
|
||||
@ -315,31 +316,127 @@ WITH (
|
||||
</pre>
|
||||
|
||||
`)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:246
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:246
|
||||
func writewithExprsTutorial(qq422016 qtio422016.Writer) {
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:246
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:246
|
||||
streamwithExprsTutorial(qw422016)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:246
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:246
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:246
|
||||
func withExprsTutorial() string {
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:246
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:246
|
||||
writewithExprsTutorial(qb422016)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:246
|
||||
qs422016 := string(qb422016.B)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:246
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:246
|
||||
return qs422016
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:245
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:246
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:249
|
||||
func StreamExpandWithExprsJSONResponse(qw422016 *qt422016.Writer, q string) {
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:250
|
||||
streamexpandWithExprsJSON(qw422016, q)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:251
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:251
|
||||
func WriteExpandWithExprsJSONResponse(qq422016 qtio422016.Writer, q string) {
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:251
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:251
|
||||
StreamExpandWithExprsJSONResponse(qw422016, q)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:251
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:251
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:251
|
||||
func ExpandWithExprsJSONResponse(q string) string {
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:251
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:251
|
||||
WriteExpandWithExprsJSONResponse(qb422016, q)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:251
|
||||
qs422016 := string(qb422016.B)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:251
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:251
|
||||
return qs422016
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:251
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:253
|
||||
func streamexpandWithExprsJSON(qw422016 *qt422016.Writer, q string) {
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:254
|
||||
if len(q) == 0 {
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:254
|
||||
qw422016.N().S(`{"status": "error","error": "query string cannot be empty"}`)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:259
|
||||
return
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:260
|
||||
}
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:260
|
||||
qw422016.N().S(`{`)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:263
|
||||
expr, err := metricsql.Parse(q)
|
||||
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:264
|
||||
if err != nil {
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:264
|
||||
qw422016.N().S(`"status": "error","error":`)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:266
|
||||
qw422016.N().Q(fmt.Sprintf("Cannot parse query: %s", err))
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:267
|
||||
} else {
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:268
|
||||
expr = metricsql.Optimize(expr)
|
||||
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:268
|
||||
qw422016.N().S(`"status": "success","expr":`)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:270
|
||||
qw422016.N().Q(string(expr.AppendString(nil)))
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:271
|
||||
}
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:271
|
||||
qw422016.N().S(`}`)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:273
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:273
|
||||
func writeexpandWithExprsJSON(qq422016 qtio422016.Writer, q string) {
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:273
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:273
|
||||
streamexpandWithExprsJSON(qw422016, q)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:273
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:273
|
||||
}
|
||||
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:273
|
||||
func expandWithExprsJSON(q string) string {
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:273
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:273
|
||||
writeexpandWithExprsJSON(qb422016, q)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:273
|
||||
qs422016 := string(qb422016.B)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:273
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:273
|
||||
return qs422016
|
||||
//line app/vmselect/prometheus/expand-with-exprs.qtpl:273
|
||||
}
|
||||
|
@ -60,10 +60,17 @@ 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")
|
||||
format := r.FormValue("format")
|
||||
bw := bufferedwriter.Get(w)
|
||||
defer bufferedwriter.Put(bw)
|
||||
WriteExpandWithExprsResponse(bw, query)
|
||||
if format == "json" {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
WriteExpandWithExprsJSONResponse(bw, query)
|
||||
} else {
|
||||
WriteExpandWithExprsResponse(bw, query)
|
||||
}
|
||||
_ = bw.Flush()
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ import ThemeProvider from "./components/Main/ThemeProvider/ThemeProvider";
|
||||
import TracePage from "./pages/TracePage";
|
||||
import ExploreMetrics from "./pages/ExploreMetrics";
|
||||
import PreviewIcons from "./components/Main/Icons/PreviewIcons";
|
||||
import WithTemplate from "./pages/WithTemplate";
|
||||
|
||||
const App: FC = () => {
|
||||
|
||||
@ -51,6 +52,10 @@ const App: FC = () => {
|
||||
path={router.dashboards}
|
||||
element={<DashboardsLayout/>}
|
||||
/>
|
||||
<Route
|
||||
path={router.withTemplate}
|
||||
element={<WithTemplate/>}
|
||||
/>
|
||||
<Route
|
||||
path={router.icons}
|
||||
element={<PreviewIcons/>}
|
||||
|
2
app/vmui/packages/vmui/src/api/expand-with-exprs.ts
Normal file
2
app/vmui/packages/vmui/src/api/expand-with-exprs.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const getExpandWithExprUrl = (server: string, query: string): string =>
|
||||
`${server}/expand-with-exprs?query=${query}&format=json`;
|
@ -45,8 +45,17 @@ const HeaderNav: FC<HeaderNavProps> = ({ color, background, direction }) => {
|
||||
]
|
||||
},
|
||||
{
|
||||
label: routerOptions[router.trace].title,
|
||||
value: router.trace,
|
||||
label: "Tools",
|
||||
submenu: [
|
||||
{
|
||||
label: routerOptions[router.trace].title,
|
||||
value: router.trace,
|
||||
},
|
||||
{
|
||||
label: routerOptions[router.withTemplate].title,
|
||||
value: router.withTemplate,
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: routerOptions[router.dashboards].title,
|
||||
|
@ -0,0 +1,45 @@
|
||||
import React, { FC, useEffect } from "preact/compat";
|
||||
import "./style.scss";
|
||||
import { useState } from "react";
|
||||
import Tooltip from "../Tooltip/Tooltip";
|
||||
import Button from "../Button/Button";
|
||||
import { CopyIcon } from "../Icons";
|
||||
|
||||
enum CopyState { copy = "Copy", copied = "Copied" }
|
||||
|
||||
const CodeExample: FC<{code: string}> = ({ code }) => {
|
||||
const [tooltip, setTooltip] = useState(CopyState.copy);
|
||||
const handlerCopy = () => {
|
||||
navigator.clipboard.writeText(code);
|
||||
setTooltip(CopyState.copied);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let timeout: NodeJS.Timeout | null = null;
|
||||
if (tooltip === CopyState.copied) {
|
||||
timeout = setTimeout(() => setTooltip(CopyState.copy), 1000);
|
||||
}
|
||||
|
||||
return () => {
|
||||
timeout && clearTimeout(timeout);
|
||||
};
|
||||
}, [tooltip]);
|
||||
|
||||
return (
|
||||
<code className="vm-code-example">
|
||||
{code}
|
||||
<div className="vm-code-example__copy">
|
||||
<Tooltip title={tooltip}>
|
||||
<Button
|
||||
size="small"
|
||||
variant="text"
|
||||
onClick={handlerCopy}
|
||||
startIcon={<CopyIcon/>}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</code>
|
||||
);
|
||||
};
|
||||
|
||||
export default CodeExample;
|
@ -0,0 +1,17 @@
|
||||
@use "src/styles/variables" as *;
|
||||
|
||||
.vm-code-example {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: $padding-global;
|
||||
white-space: pre-wrap;
|
||||
border-radius: $border-radius-small;
|
||||
background-color: rgba($color-black, 0.05);
|
||||
overflow: auto;
|
||||
|
||||
&__copy {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@ import {
|
||||
const spinnerMessage = `Please wait while cardinality stats is calculated.
|
||||
This may take some time if the db contains big number of time series.`;
|
||||
|
||||
const Index: FC = () => {
|
||||
const CardinalityPanel: FC = () => {
|
||||
const { isMobile } = useDeviceDetect();
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
@ -88,4 +88,4 @@ const Index: FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
||||
export default CardinalityPanel;
|
||||
|
@ -19,9 +19,8 @@ import classNames from "classnames";
|
||||
|
||||
const exampleDuration = "30ms, 15s, 3d4h, 1y2w";
|
||||
|
||||
const Index: FC = () => {
|
||||
const TopQueries: FC = () => {
|
||||
const { isMobile } = useDeviceDetect();
|
||||
|
||||
const { data, error, loading } = useFetchTopQueries();
|
||||
const { topN, maxLifetime } = useTopQueriesState();
|
||||
const topQueriesDispatch = useTopQueriesDispatch();
|
||||
@ -180,4 +179,4 @@ const Index: FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
||||
export default TopQueries;
|
||||
|
@ -0,0 +1,218 @@
|
||||
import React, { FC } from "preact/compat";
|
||||
import "./style.scss";
|
||||
import CodeExample from "../../../components/Main/CodeExample/CodeExample";
|
||||
|
||||
const MetricsQL = () => (
|
||||
<a
|
||||
className="vm-link vm-link_colored"
|
||||
href="https://docs.victoriametrics.com/MetricsQL.html"
|
||||
target="_blank"
|
||||
rel="help noreferrer"
|
||||
>
|
||||
MetricsQL
|
||||
</a>
|
||||
);
|
||||
|
||||
const NodeExporterFull = () => (
|
||||
<a
|
||||
className="vm-link vm-link_colored"
|
||||
href="https://grafana.com/grafana/dashboards/1860-node-exporter-full/"
|
||||
target="_blank"
|
||||
rel="help noreferrer"
|
||||
>
|
||||
Node Exporter Full
|
||||
</a>
|
||||
);
|
||||
|
||||
const WithTemplateTutorial: FC = () => (
|
||||
<section className="vm-with-template-tutorial">
|
||||
<h2 className="vm-with-template-tutorial__title">
|
||||
Tutorial for WITH expressions in <MetricsQL/>
|
||||
</h2>
|
||||
<div className="vm-with-template-tutorial-section">
|
||||
<p className="vm-with-template-tutorial-section__text">Let's look at the following real query from <NodeExporterFull/> dashboard:</p>
|
||||
<CodeExample
|
||||
code= {`(
|
||||
(
|
||||
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`}
|
||||
/>
|
||||
<p className="vm-with-template-tutorial-section__text">
|
||||
It is clear the query calculates the percentage of used memory for the given $node, $port and $job.
|
||||
Isn't it? :)
|
||||
</p>
|
||||
</div>
|
||||
<div className="vm-with-template-tutorial-section">
|
||||
<p className="vm-with-template-tutorial-section__text">
|
||||
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>
|
||||
<CodeExample
|
||||
code={`WITH (
|
||||
commonFilters = {instance=~"$node:$port",job=~"$job"}
|
||||
)
|
||||
(
|
||||
node_memory_MemTotal_bytes{commonFilters}
|
||||
-
|
||||
node_memory_MemFree_bytes{commonFilters}
|
||||
)
|
||||
/
|
||||
node_memory_MemTotal_bytes{commonFilters} * 100`}
|
||||
/>
|
||||
</div>
|
||||
<div className="vm-with-template-tutorial-section">
|
||||
<p className="vm-with-template-tutorial-section__text">
|
||||
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>
|
||||
<CodeExample
|
||||
code={`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"},
|
||||
)`}
|
||||
/>
|
||||
<p className="vm-with-template-tutorial-section__text">
|
||||
Now the template function my_resource_utilization() may be used
|
||||
for monitoring arbitrary resources - memory, CPU, network, storage, you name it.
|
||||
</p>
|
||||
</div>
|
||||
<div className="vm-with-template-tutorial-section">
|
||||
<p className="vm-with-template-tutorial-section__text">
|
||||
Let's take another nice query from <NodeExporterFull/> dashboard:
|
||||
</p>
|
||||
<CodeExample
|
||||
code={`(
|
||||
(
|
||||
(
|
||||
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)
|
||||
)`}
|
||||
/>
|
||||
<p className="vm-with-template-tutorial-section__text">
|
||||
Do you understand what does this mess do? Is it manageable? :)
|
||||
WITH expressions are happy to help in a few iterations.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="vm-with-template-tutorial-section">
|
||||
<p className="vm-with-template-tutorial-section__text">
|
||||
1. Extract common filters used in multiple places into a commonFilters variable:
|
||||
</p>
|
||||
<CodeExample
|
||||
code={`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)
|
||||
)`}
|
||||
/>
|
||||
</div>
|
||||
<div className="vm-with-template-tutorial-section">
|
||||
<p className="vm-with-template-tutorial-section__text">
|
||||
2. Extract "count(count(...) by (cpu))" into cpuCount variable:
|
||||
</p>
|
||||
<CodeExample
|
||||
code={`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`}
|
||||
/>
|
||||
</div>
|
||||
<div className="vm-with-template-tutorial-section">
|
||||
<p className="vm-with-template-tutorial-section__text">
|
||||
3. Extract rate(...) part into cpuIdle variable,
|
||||
since it is clear now that this part calculates the number of idle CPUs:
|
||||
</p>
|
||||
<CodeExample
|
||||
code={`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`}
|
||||
/>
|
||||
</div>
|
||||
<div className="vm-with-template-tutorial-section">
|
||||
<p className="vm-with-template-tutorial-section__text">
|
||||
4. Put node_cpu_seconds_total{"{commonFilters}"} into its own varialbe with the name cpuSeconds:
|
||||
</p>
|
||||
<CodeExample
|
||||
code={`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`}
|
||||
/>
|
||||
<p className="vm-with-template-tutorial-section__text">
|
||||
Now the query became more clear comparing to the initial query.
|
||||
</p>
|
||||
</div>
|
||||
<div className="vm-with-template-tutorial-section">
|
||||
<p className="vm-with-template-tutorial-section__text">
|
||||
WITH expressions may be nested and may be put anywhere. Try expanding the following query:
|
||||
</p>
|
||||
<CodeExample
|
||||
code= {`WITH (
|
||||
f(a, b) = WITH (
|
||||
f1(x) = b-x,
|
||||
f2(x) = x+x
|
||||
) f1(a)*f2(b)
|
||||
) f(foo, with(x=bar) x)`}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
export default WithTemplateTutorial;
|
@ -0,0 +1,22 @@
|
||||
@use "src/styles/variables" as *;
|
||||
|
||||
.vm-with-template-tutorial {
|
||||
display: grid;
|
||||
gap: $padding-large;
|
||||
|
||||
&__title {
|
||||
font-size: $font-size-large;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&-section {
|
||||
display: grid;
|
||||
gap: $padding-global;
|
||||
|
||||
&__text {
|
||||
font-size: $font-size-medium;
|
||||
line-height: 130%;
|
||||
max-width: 720px;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
import { useAppState } from "../../../state/common/StateContext";
|
||||
import { useState } from "react";
|
||||
import { ErrorTypes } from "../../../types";
|
||||
import { getExpandWithExprUrl } from "../../../api/expand-with-exprs";
|
||||
|
||||
export const useExpandWithExprs = () => {
|
||||
const { serverUrl } = useAppState();
|
||||
|
||||
const [data, setData] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<ErrorTypes | string>();
|
||||
|
||||
const fetchData = async (query: string) => {
|
||||
const fetchUrl = getExpandWithExprUrl(serverUrl, query);
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetch(fetchUrl);
|
||||
|
||||
const resp = await response.json();
|
||||
|
||||
setData(resp?.expr || "");
|
||||
setError(String(resp.error || ""));
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.name !== "AbortError") {
|
||||
setError(`${e.name}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return {
|
||||
data,
|
||||
error,
|
||||
loading,
|
||||
expand: fetchData
|
||||
};
|
||||
};
|
63
app/vmui/packages/vmui/src/pages/WithTemplate/index.tsx
Normal file
63
app/vmui/packages/vmui/src/pages/WithTemplate/index.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import React, { FC } from "preact/compat";
|
||||
import "./style.scss";
|
||||
import TextField from "../../components/Main/TextField/TextField";
|
||||
import { useState } from "react";
|
||||
import Button from "../../components/Main/Button/Button";
|
||||
import { PlayIcon } from "../../components/Main/Icons";
|
||||
import WithTemplateTutorial from "./WithTemplateTutorial/WithTemplateTutorial";
|
||||
import { useExpandWithExprs } from "./hooks/useExpandWithExprs";
|
||||
import Spinner from "../../components/Main/Spinner/Spinner";
|
||||
|
||||
const WithTemplate: FC = () => {
|
||||
const { data, loading, error, expand } = useExpandWithExprs();
|
||||
const [expr, setExpr] = useState("");
|
||||
|
||||
const handleChangeInput = (val: string) => {
|
||||
setExpr(val);
|
||||
};
|
||||
|
||||
const handleRunQuery = () => {
|
||||
expand(expr);
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="vm-with-template">
|
||||
{loading && <Spinner />}
|
||||
|
||||
<div className="vm-with-template-body vm-block">
|
||||
<div className="vm-with-template-body__expr">
|
||||
<TextField
|
||||
type="textarea"
|
||||
label="MetricsQL query with optional WITH expressions"
|
||||
value={expr}
|
||||
error={error}
|
||||
autofocus
|
||||
onChange={handleChangeInput}
|
||||
/>
|
||||
</div>
|
||||
<div className="vm-with-template-body__result">
|
||||
<TextField
|
||||
type="textarea"
|
||||
label="MetricsQL query after expanding WITH expressions and applying other optimizations"
|
||||
value={data}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div className="vm-with-template-body-top">
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleRunQuery}
|
||||
startIcon={<PlayIcon/>}
|
||||
>
|
||||
Expand
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="vm-block">
|
||||
<WithTemplateTutorial/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default WithTemplate;
|
34
app/vmui/packages/vmui/src/pages/WithTemplate/style.scss
Normal file
34
app/vmui/packages/vmui/src/pages/WithTemplate/style.scss
Normal file
@ -0,0 +1,34 @@
|
||||
@use "src/styles/variables" as *;
|
||||
|
||||
.vm-with-template {
|
||||
display: grid;
|
||||
gap: $padding-medium;
|
||||
|
||||
&-body {
|
||||
display: grid;
|
||||
gap: $padding-global;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
|
||||
&-top {
|
||||
display: flex;
|
||||
gap: $padding-small;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&__expr textarea {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
&__result textarea {
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,8 @@ const router = {
|
||||
cardinality: "/cardinality",
|
||||
topQueries: "/top-queries",
|
||||
trace: "/trace",
|
||||
icons: "/icons"
|
||||
withTemplate: "/expand-with-exprs",
|
||||
icons: "/icons" // for dev
|
||||
};
|
||||
|
||||
export interface RouterOptionsHeader {
|
||||
@ -65,6 +66,10 @@ export const routerOptions: {[key: string]: RouterOptions} = {
|
||||
title: "Dashboards",
|
||||
...routerOptionsDefault,
|
||||
},
|
||||
[router.withTemplate]: {
|
||||
title: "WITH templates",
|
||||
header: {}
|
||||
},
|
||||
[router.icons]: {
|
||||
title: "Icons",
|
||||
header: {}
|
||||
|
@ -160,6 +160,7 @@ Released at 2023-02-24
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): show `median` instead of `avg` in graph tooltip and line legend, since `median` is more tolerant against spikes. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3706).
|
||||
* FEATURE: add `-search.maxSeriesPerAggrFunc` command-line flag, which can be used for limiting the number of time series [MetricsQL aggregate functions](https://docs.victoriametrics.com/MetricsQL.html#aggregate-functions) can return in a single query. This flag can be useful for preventing OOMs when [count_values](https://docs.victoriametrics.com/MetricsQL.html#count_values) function is improperly used.
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): small UX improvements for mobile view. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3707) and [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3848).
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): integrate WITH template playground. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3811).
|
||||
* FEATURE: add `-search.logQueryMemoryUsage` command-line flag for logging queries, which need more memory than specified by this command-line flag. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3553). Thanks to @michal-kralik for the idea and the intial implementation.
|
||||
* FEATURE: allow setting zero value for `-search.latencyOffset` command-line flag. This may be needed in [some cases](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2061#issuecomment-1299109836). Previously the minimum supported value for `-search.latencyOffset` command-line flag was `1s`.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user