From 5ebfc275e6110398a76abd612d179b670870e118 Mon Sep 17 00:00:00 2001 From: Aliaksandr Valialkin <valyala@gmail.com> Date: Mon, 14 Dec 2020 13:08:22 +0200 Subject: [PATCH 1/5] app/victoria-metrics: automatically reset response cache when samples with too timestamps older than `now - search.cacheTimestampOffset` are ingested --- README.md | 3 +- app/victoria-metrics/main.go | 3 +- app/victoria-metrics/main_test.go | 5 ++-- app/vmselect/promql/rollup_result_cache.go | 34 ++++++++++++++++++++++ app/vmstorage/main.go | 11 +++++-- docs/CHANGELOG.md | 1 + docs/Single-server-VictoriaMetrics.md | 3 +- 7 files changed, 52 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 045c3d000b..cb81548c10 100644 --- a/README.md +++ b/README.md @@ -1332,7 +1332,8 @@ An alternative solution is to query `/internal/resetRollupResultCache` url after the query cache, which could contain incomplete data cached during the backfilling. Yet another solution is to increase `-search.cacheTimestampOffset` flag value in order to disable caching -for data with timestamps close to the current time. +for data with timestamps close to the current time. Single-node VictoriaMetrics automatically resets response +cache when samples with timestamps older than `now - search.cacheTimestampOffset` are ingested to it. ## Data updates diff --git a/app/victoria-metrics/main.go b/app/victoria-metrics/main.go index c74d747682..339095e227 100644 --- a/app/victoria-metrics/main.go +++ b/app/victoria-metrics/main.go @@ -9,6 +9,7 @@ import ( "github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert" "github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect" + "github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/promql" "github.com/VictoriaMetrics/VictoriaMetrics/app/vmstorage" "github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo" "github.com/VictoriaMetrics/VictoriaMetrics/lib/envflag" @@ -50,7 +51,7 @@ func main() { logger.Infof("starting VictoriaMetrics at %q...", *httpListenAddr) startTime := time.Now() storage.SetMinScrapeIntervalForDeduplication(*minScrapeInterval) - vmstorage.Init() + vmstorage.Init(promql.ResetRollupResultCacheIfNeeded) vmselect.Init() vminsert.Init() startSelfScraper() diff --git a/app/victoria-metrics/main_test.go b/app/victoria-metrics/main_test.go index 6eb2c13c9c..9261576239 100644 --- a/app/victoria-metrics/main_test.go +++ b/app/victoria-metrics/main_test.go @@ -20,6 +20,7 @@ import ( testutil "github.com/VictoriaMetrics/VictoriaMetrics/app/victoria-metrics/test" "github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert" "github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect" + "github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/promql" "github.com/VictoriaMetrics/VictoriaMetrics/app/vmstorage" "github.com/VictoriaMetrics/VictoriaMetrics/lib/envflag" "github.com/VictoriaMetrics/VictoriaMetrics/lib/fs" @@ -129,7 +130,7 @@ func setUp() { storagePath = filepath.Join(os.TempDir(), testStorageSuffix) processFlags() logger.Init() - vmstorage.InitWithoutMetrics() + vmstorage.InitWithoutMetrics(promql.ResetRollupResultCacheIfNeeded) vmselect.Init() vminsert.Init() go httpserver.Serve(*httpListenAddr, requestHandler) @@ -192,7 +193,7 @@ func TestWriteRead(t *testing.T) { time.Sleep(1 * time.Second) vmstorage.Stop() // open storage after stop in write - vmstorage.InitWithoutMetrics() + vmstorage.InitWithoutMetrics(promql.ResetRollupResultCacheIfNeeded) t.Run("read", testRead) } diff --git a/app/vmselect/promql/rollup_result_cache.go b/app/vmselect/promql/rollup_result_cache.go index db6f289315..8ef54398e2 100644 --- a/app/vmselect/promql/rollup_result_cache.go +++ b/app/vmselect/promql/rollup_result_cache.go @@ -13,6 +13,7 @@ import ( "github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime" "github.com/VictoriaMetrics/VictoriaMetrics/lib/logger" "github.com/VictoriaMetrics/VictoriaMetrics/lib/memory" + "github.com/VictoriaMetrics/VictoriaMetrics/lib/storage" "github.com/VictoriaMetrics/VictoriaMetrics/lib/workingsetcache" "github.com/VictoriaMetrics/fastcache" "github.com/VictoriaMetrics/metrics" @@ -25,6 +26,39 @@ var ( "due to time synchronization issues between VictoriaMetrics and data sources") ) +// ResetRollupResultCacheIfNeeded resets rollup result cache if mrs contains timestamps outside `now - search.cacheTimestampOffset`. +func ResetRollupResultCacheIfNeeded(mrs []storage.MetricRow) { + checkRollupResultCacheResetOnce.Do(func() { + go checkRollupResultCacheReset() + }) + minTimestamp := int64(fasttime.UnixTimestamp()*1000) - cacheTimestampOffset.Milliseconds() + checkRollupResultCacheResetInterval.Milliseconds() + needCacheReset := false + for i := range mrs { + if mrs[i].Timestamp < minTimestamp { + needCacheReset = true + break + } + } + if needCacheReset { + // Do not call ResetRollupResultCache() here, since it may be heavy when frequently called. + atomic.StoreUint32(&needRollupResultCacheReset, 1) + } +} + +func checkRollupResultCacheReset() { + for { + time.Sleep(checkRollupResultCacheResetInterval) + if atomic.SwapUint32(&needRollupResultCacheReset, 0) > 0 { + ResetRollupResultCache() + } + } +} + +const checkRollupResultCacheResetInterval = 5 * time.Second + +var needRollupResultCacheReset uint32 +var checkRollupResultCacheResetOnce sync.Once + var rollupResultCacheV = &rollupResultCache{ c: workingsetcache.New(1024*1024, time.Hour), // This is a cache for testing. } diff --git a/app/vmstorage/main.go b/app/vmstorage/main.go index cee1db78c3..9207084225 100644 --- a/app/vmstorage/main.go +++ b/app/vmstorage/main.go @@ -57,19 +57,20 @@ func CheckTimeRange(tr storage.TimeRange) error { } // Init initializes vmstorage. -func Init() { - InitWithoutMetrics() +func Init(resetCacheIfNeeded func(mrs []storage.MetricRow)) { + InitWithoutMetrics(resetCacheIfNeeded) registerStorageMetrics() } // InitWithoutMetrics must be called instead of Init inside tests. // // This allows multiple Init / Stop cycles. -func InitWithoutMetrics() { +func InitWithoutMetrics(resetCacheIfNeeded func(mrs []storage.MetricRow)) { if err := encoding.CheckPrecisionBits(uint8(*precisionBits)); err != nil { logger.Fatalf("invalid `-precisionBits`: %s", err) } + resetResponseCacheIfNeeded = resetCacheIfNeeded storage.SetFinalMergeDelay(*finalMergeDelay) storage.SetBigMergeWorkersCount(*bigMergeConcurrency) storage.SetSmallMergeWorkersCount(*smallMergeConcurrency) @@ -105,8 +106,12 @@ var Storage *storage.Storage // Use syncwg instead of sync, since Add is called from concurrent goroutines. var WG syncwg.WaitGroup +// resetResponseCacheIfNeeded is a callback for automatic resetting of response cache if needed. +var resetResponseCacheIfNeeded func(mrs []storage.MetricRow) + // AddRows adds mrs to the storage. func AddRows(mrs []storage.MetricRow) error { + resetResponseCacheIfNeeded(mrs) WG.Add(1) err := Storage.AddRows(mrs, uint8(*precisionBits)) WG.Done() diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3c2afbff71..f8b88b4819 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,7 @@ # tip +* FEATURE: automatically reset response cache when samples with timestamps older than `now - search.cacheTimestampOffset` are ingested to VictoriaMetrics. This makes unnecessary disabling response cache during data backfilling or resetting it after backfilling is complete as described [in these docs](https://victoriametrics.github.io/#backfilling). This feature applies only to single-node VictoriaMetrics. It doesn't apply to cluster version of VictoriaMetrics because `vminsert` nodes don't know about `vmselect` nodes where the response cache must be reset. * FEATURE: allow multiple whitespace chars between measurements, fields and timestamp when parsing InfluxDB line protocol. Though [InfluxDB line protocol](https://docs.influxdata.com/influxdb/v1.8/write_protocols/line_protocol_tutorial/) denies multiple whitespace chars between these entities, some apps improperly put multiple whitespace chars. This workaround allows accepting data from such apps. diff --git a/docs/Single-server-VictoriaMetrics.md b/docs/Single-server-VictoriaMetrics.md index 045c3d000b..cb81548c10 100644 --- a/docs/Single-server-VictoriaMetrics.md +++ b/docs/Single-server-VictoriaMetrics.md @@ -1332,7 +1332,8 @@ An alternative solution is to query `/internal/resetRollupResultCache` url after the query cache, which could contain incomplete data cached during the backfilling. Yet another solution is to increase `-search.cacheTimestampOffset` flag value in order to disable caching -for data with timestamps close to the current time. +for data with timestamps close to the current time. Single-node VictoriaMetrics automatically resets response +cache when samples with timestamps older than `now - search.cacheTimestampOffset` are ingested to it. ## Data updates From ce8c2dd1f1b3c6a5aad400e2fcc360e5bfaaba7b Mon Sep 17 00:00:00 2001 From: Nikolay <nik@victoriametrics.com> Date: Mon, 14 Dec 2020 14:36:48 +0300 Subject: [PATCH 2/5] Changes targets api (#961) * changes /targets api adds html response if requester accepts text/html, adds quick template for /targets api, fixes pathPrefix for / requests * changes namings * renamed targets file * Update app/victoria-metrics/main.go Co-authored-by: Aliaksandr Valialkin <valyala@gmail.com> * adds trimspace to qtpl, moves content-type for targets response closer to writer * fixes bug with prefix Co-authored-by: Aliaksandr Valialkin <valyala@gmail.com> --- app/victoria-metrics/main.go | 23 +- app/vmagent/main.go | 8 +- app/vminsert/main.go | 8 +- lib/httpserver/httpserver.go | 10 +- lib/promscrape/targets_response.qtpl | 104 +++++++ lib/promscrape/targets_response.qtpl.go | 343 ++++++++++++++++++++++++ lib/promscrape/targetstatus.go | 155 ++++++----- 7 files changed, 583 insertions(+), 68 deletions(-) create mode 100644 lib/promscrape/targets_response.qtpl create mode 100644 lib/promscrape/targets_response.qtpl.go diff --git a/app/victoria-metrics/main.go b/app/victoria-metrics/main.go index 339095e227..0aa5ec3f54 100644 --- a/app/victoria-metrics/main.go +++ b/app/victoria-metrics/main.go @@ -3,8 +3,10 @@ package main import ( "flag" "fmt" + "io" "net/http" "os" + "path" "time" "github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert" @@ -81,8 +83,16 @@ func main() { } func requestHandler(w http.ResponseWriter, r *http.Request) bool { - if r.RequestURI == "/" { - fmt.Fprintf(w, "Single-node VictoriaMetrics. See docs at https://victoriametrics.github.io/") + if r.URL.Path == "/" { + fmt.Fprintf(w, "<h2>Single-node VictoriaMetrics.</h2></br>") + fmt.Fprintf(w, "See docs at <a href='https://victoriametrics.github.io/'>https://victoriametrics.github.io/</a></br>") + fmt.Fprintf(w, "usefull apis: </br>") + writeAPIHelp(w, [][]string{ + {"/targets", "discovered targets list"}, + {"/api/v1/targets", "advanced information about discovered targets in JSON format"}, + {"/metrics", "available service metrics"}, + {"/api/v1/status/tsdb", "tsdb status page"}, + }) return true } if vminsert.RequestHandler(w, r) { @@ -96,3 +106,12 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool { } return false } + +func writeAPIHelp(w io.Writer, pathList [][]string) { + pathPrefix := httpserver.GetPathPrefix() + for _, p := range pathList { + p, doc := p[0], p[1] + p = path.Join(pathPrefix, p) + fmt.Fprintf(w, "<a href='%s'>%q</a> - %s<br/>", p, p, doc) + } +} diff --git a/app/vmagent/main.go b/app/vmagent/main.go index a66bbce222..3c429c15c6 100644 --- a/app/vmagent/main.go +++ b/app/vmagent/main.go @@ -212,9 +212,13 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool { return true case "/targets": promscrapeTargetsRequests.Inc() - w.Header().Set("Content-Type", "text/plain; charset=utf-8") showOriginalLabels, _ := strconv.ParseBool(r.FormValue("show_original_labels")) - promscrape.WriteHumanReadableTargetsStatus(w, showOriginalLabels) + showOnlyUnhealthy, _ := strconv.ParseBool(r.FormValue("show_only_unhealthy")) + if accept := r.Header.Get("accept"); strings.Contains(accept, "text/html") { + promscrape.WriteHumanReadableTargetsStatus(w, showOriginalLabels, showOnlyUnhealthy, "html") + return true + } + promscrape.WriteHumanReadableTargetsStatus(w, showOriginalLabels, showOnlyUnhealthy, "plain") return true case "/api/v1/targets": promscrapeAPIV1TargetsRequests.Inc() diff --git a/app/vminsert/main.go b/app/vminsert/main.go index 3bfd7f2c33..c8f27da05a 100644 --- a/app/vminsert/main.go +++ b/app/vminsert/main.go @@ -155,9 +155,13 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool { return true case "/targets": promscrapeTargetsRequests.Inc() - w.Header().Set("Content-Type", "text/plain; charset=utf-8") showOriginalLabels, _ := strconv.ParseBool(r.FormValue("show_original_labels")) - promscrape.WriteHumanReadableTargetsStatus(w, showOriginalLabels) + showOnlyUnhealthy, _ := strconv.ParseBool(r.FormValue("show_only_unhealthy")) + if accept := r.Header.Get("accept"); strings.Contains(accept, "text/html") { + promscrape.WriteHumanReadableTargetsStatus(w, showOriginalLabels, showOnlyUnhealthy, "html") + return true + } + promscrape.WriteHumanReadableTargetsStatus(w, showOriginalLabels, showOnlyUnhealthy, "plain") return true case "/api/v1/targets": promscrapeAPIV1TargetsRequests.Inc() diff --git a/lib/httpserver/httpserver.go b/lib/httpserver/httpserver.go index daa8c8e59f..6a760af5bd 100644 --- a/lib/httpserver/httpserver.go +++ b/lib/httpserver/httpserver.go @@ -274,9 +274,12 @@ func handlerWrapper(s *server, w http.ResponseWriter, r *http.Request, rh Reques } func getCanonicalPath(path string) (string, error) { - if len(*pathPrefix) == 0 { + if len(*pathPrefix) == 0 || path == "/" { return path, nil } + if *pathPrefix == path { + return "/", nil + } prefix := *pathPrefix if !strings.HasSuffix(prefix, "/") { prefix = prefix + "/" @@ -573,3 +576,8 @@ func isTrivialNetworkError(err error) bool { func IsTLS() bool { return *tlsEnable } + +// GetPathPrefix - returns http server path prefix. +func GetPathPrefix() string { + return *pathPrefix +} diff --git a/lib/promscrape/targets_response.qtpl b/lib/promscrape/targets_response.qtpl new file mode 100644 index 0000000000..7f3d1efe33 --- /dev/null +++ b/lib/promscrape/targets_response.qtpl @@ -0,0 +1,104 @@ +{% import "github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal" +%} + +{% stripspace %} + +{% func TargetsResponsePlain (jts []jobTargetsStatuses, showOriginLabels bool) -%} + +{% for _, js := range jts %} +job={%q= js.job %}{% space %} ({%d js.upCount %}/{%d js.targetsTotal %} {% space %} up) +{% newline %} +{% for _, ts := range js.targetsStatus %} + {% code + labels := promLabelsString(ts.labels) + ol := promLabelsString(ts.originalLabels) + %} +{%s= "\t" %}state={% if ts.up %}up{% else %}down{% endif %}, + {% space %} endpoint={%s= ts.endpoint %}, + {% space %} labels={%s= labels %} + {% if showOriginLabels %},{% space %} originalLabels={%s= ol %}{% endif %}, + {% space %} last_scrape={%f.3 ts.lastScrapeTime.Seconds() %}s ago, + {% space %} scrape_duration={%f.3 float64(ts.scrapeDuration.Seconds()) %}s, + {% space %} error={%q= ts.error %} + {% newline %} +{% endfor %} +{% endfor %} +{% newline %} + +{% endfunc %} + +{% func TargetsResponseHTML(jts []jobTargetsStatuses, redirectPath string, onlyUnhealthy bool) %} +<!DOCTYPE html> +<style> + .border{ + border-collapse: collapse; + border: 1px solid black; + } + .table-row:hover{ + background-color: #f5f5f5; + } +</style> +<html lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <title>VictoriaMetrics Database</title> + </head> + <body> + <h1>Targets</h1> + <div id="showTargets" class="btn-group btn-group-toggle" data-toggle="buttons"> + <label class="btn"> + <input type="radio" name="targets" id="all-targets" autocomplete="off" onclick="location.href='{%s= redirectPath %}';" {% if !onlyUnhealthy %}checked {% endif %}> All + </label> + <label class="btn"> + <input type="radio" name="targets" id="unhealthy-targets" autocomplete="off" onclick="location.href='{%s= redirectPath %}?show_only_unhealthy=true';" {% if onlyUnhealthy %}checked {% endif %}> Unhealthy + </label> + <br /> + </div> + {% for _,js :=range jts %} + <div class="table-container"> + <h2 class="job_header danger"> + <a id="job-{%q= js.job %}" >{%q= js.job %} ({%d js.upCount %}/{%d js.targetsTotal %} up)</a> + </h2> + <table class="table-bordered table-hover border"> + <thead class="job_details border"> + <tr class="table-row border"> + <th class="border">Endpoint</th> + <th class="border">State</th> + <th class="border">Labels</th> + <th class="border">Last Scrape</th> + <th class="border">Scrape Duration</th> + <th class="border">Error</th> + </tr> + </thead> + <tbody> + {% for _, ts := range js.targetsStatus %} + {% if onlyUnhealthy && ts.up %} {% continue %} {% endif %} + <tr class="table-row border"> + <td class="endpoint border"> + <a href="{%s= ts.endpoint %}">{%s= ts.endpoint %}</a><br> + </td> + <td class="state border"> + <span class="state_indicator">{% if ts.up %}UP{% else %}DOWN{% endif %}</span> + </td> + <td class="labels border", title="Original {% space %} labels: {% space %} {%= formatLabel(ts.originalLabels) %}"> + {%= formatLabel(ts.labels) %} + </td> + <td class="last-scrape border">{%s ts.lastScrapeTime.String() %} ago</td> + <td class="scrape-duration border">{%s ts.scrapeDuration.String() %}</td> + <td class="errors border"><span class="alert alert-danger state_indicator">{%s= ts.error %}</span></td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + {% endfor %} + </body> +</html> +{% endfunc %} + +{% func formatLabel(labels []prompbmarshal.Label) %} +{% for _, label := range labels %} + {% space %} {%s label.Name %}={%q label.Value %} {% space %} +{% endfor %} +{% endfunc %} +{% endstripspace %} \ No newline at end of file diff --git a/lib/promscrape/targets_response.qtpl.go b/lib/promscrape/targets_response.qtpl.go new file mode 100644 index 0000000000..f5e319f065 --- /dev/null +++ b/lib/promscrape/targets_response.qtpl.go @@ -0,0 +1,343 @@ +// Code generated by qtc from "targets_response.qtpl". DO NOT EDIT. +// See https://github.com/valyala/quicktemplate for details. + +//line lib/promscrape/targets_response.qtpl:1 +package promscrape + +//line lib/promscrape/targets_response.qtpl:1 +import "github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal" + +//line lib/promscrape/targets_response.qtpl:6 +import ( + qtio422016 "io" + + qt422016 "github.com/valyala/quicktemplate" +) + +//line lib/promscrape/targets_response.qtpl:6 +var ( + _ = qtio422016.Copy + _ = qt422016.AcquireByteBuffer +) + +//line lib/promscrape/targets_response.qtpl:6 +func StreamTargetsResponsePlain(qw422016 *qt422016.Writer, jts []jobTargetsStatuses, showOriginLabels bool) { +//line lib/promscrape/targets_response.qtpl:8 + for _, js := range jts { +//line lib/promscrape/targets_response.qtpl:8 + qw422016.N().S(`job=`) +//line lib/promscrape/targets_response.qtpl:9 + qw422016.N().Q(js.job) +//line lib/promscrape/targets_response.qtpl:9 + qw422016.N().S(` `) +//line lib/promscrape/targets_response.qtpl:9 + qw422016.N().S(`(`) +//line lib/promscrape/targets_response.qtpl:9 + qw422016.N().D(js.upCount) +//line lib/promscrape/targets_response.qtpl:9 + qw422016.N().S(`/`) +//line lib/promscrape/targets_response.qtpl:9 + qw422016.N().D(js.targetsTotal) +//line lib/promscrape/targets_response.qtpl:9 + qw422016.N().S(` `) +//line lib/promscrape/targets_response.qtpl:9 + qw422016.N().S(`up)`) +//line lib/promscrape/targets_response.qtpl:10 + qw422016.N().S(` +`) +//line lib/promscrape/targets_response.qtpl:11 + for _, ts := range js.targetsStatus { +//line lib/promscrape/targets_response.qtpl:13 + labels := promLabelsString(ts.labels) + ol := promLabelsString(ts.originalLabels) + +//line lib/promscrape/targets_response.qtpl:16 + qw422016.N().S("\t") +//line lib/promscrape/targets_response.qtpl:16 + qw422016.N().S(`state=`) +//line lib/promscrape/targets_response.qtpl:16 + if ts.up { +//line lib/promscrape/targets_response.qtpl:16 + qw422016.N().S(`up`) +//line lib/promscrape/targets_response.qtpl:16 + } else { +//line lib/promscrape/targets_response.qtpl:16 + qw422016.N().S(`down`) +//line lib/promscrape/targets_response.qtpl:16 + } +//line lib/promscrape/targets_response.qtpl:16 + qw422016.N().S(`,`) +//line lib/promscrape/targets_response.qtpl:17 + qw422016.N().S(` `) +//line lib/promscrape/targets_response.qtpl:17 + qw422016.N().S(`endpoint=`) +//line lib/promscrape/targets_response.qtpl:17 + qw422016.N().S(ts.endpoint) +//line lib/promscrape/targets_response.qtpl:17 + qw422016.N().S(`,`) +//line lib/promscrape/targets_response.qtpl:18 + qw422016.N().S(` `) +//line lib/promscrape/targets_response.qtpl:18 + qw422016.N().S(`labels=`) +//line lib/promscrape/targets_response.qtpl:18 + qw422016.N().S(labels) +//line lib/promscrape/targets_response.qtpl:19 + if showOriginLabels { +//line lib/promscrape/targets_response.qtpl:19 + qw422016.N().S(`,`) +//line lib/promscrape/targets_response.qtpl:19 + qw422016.N().S(` `) +//line lib/promscrape/targets_response.qtpl:19 + qw422016.N().S(`originalLabels=`) +//line lib/promscrape/targets_response.qtpl:19 + qw422016.N().S(ol) +//line lib/promscrape/targets_response.qtpl:19 + } +//line lib/promscrape/targets_response.qtpl:19 + qw422016.N().S(`,`) +//line lib/promscrape/targets_response.qtpl:20 + qw422016.N().S(` `) +//line lib/promscrape/targets_response.qtpl:20 + qw422016.N().S(`last_scrape=`) +//line lib/promscrape/targets_response.qtpl:20 + qw422016.N().FPrec(ts.lastScrapeTime.Seconds(), 3) +//line lib/promscrape/targets_response.qtpl:20 + qw422016.N().S(`s ago,`) +//line lib/promscrape/targets_response.qtpl:21 + qw422016.N().S(` `) +//line lib/promscrape/targets_response.qtpl:21 + qw422016.N().S(`scrape_duration=`) +//line lib/promscrape/targets_response.qtpl:21 + qw422016.N().FPrec(float64(ts.scrapeDuration.Seconds()), 3) +//line lib/promscrape/targets_response.qtpl:21 + qw422016.N().S(`s,`) +//line lib/promscrape/targets_response.qtpl:22 + qw422016.N().S(` `) +//line lib/promscrape/targets_response.qtpl:22 + qw422016.N().S(`error=`) +//line lib/promscrape/targets_response.qtpl:22 + qw422016.N().Q(ts.error) +//line lib/promscrape/targets_response.qtpl:23 + qw422016.N().S(` +`) +//line lib/promscrape/targets_response.qtpl:24 + } +//line lib/promscrape/targets_response.qtpl:25 + } +//line lib/promscrape/targets_response.qtpl:26 + qw422016.N().S(` +`) +//line lib/promscrape/targets_response.qtpl:28 +} + +//line lib/promscrape/targets_response.qtpl:28 +func WriteTargetsResponsePlain(qq422016 qtio422016.Writer, jts []jobTargetsStatuses, showOriginLabels bool) { +//line lib/promscrape/targets_response.qtpl:28 + qw422016 := qt422016.AcquireWriter(qq422016) +//line lib/promscrape/targets_response.qtpl:28 + StreamTargetsResponsePlain(qw422016, jts, showOriginLabels) +//line lib/promscrape/targets_response.qtpl:28 + qt422016.ReleaseWriter(qw422016) +//line lib/promscrape/targets_response.qtpl:28 +} + +//line lib/promscrape/targets_response.qtpl:28 +func TargetsResponsePlain(jts []jobTargetsStatuses, showOriginLabels bool) string { +//line lib/promscrape/targets_response.qtpl:28 + qb422016 := qt422016.AcquireByteBuffer() +//line lib/promscrape/targets_response.qtpl:28 + WriteTargetsResponsePlain(qb422016, jts, showOriginLabels) +//line lib/promscrape/targets_response.qtpl:28 + qs422016 := string(qb422016.B) +//line lib/promscrape/targets_response.qtpl:28 + qt422016.ReleaseByteBuffer(qb422016) +//line lib/promscrape/targets_response.qtpl:28 + return qs422016 +//line lib/promscrape/targets_response.qtpl:28 +} + +//line lib/promscrape/targets_response.qtpl:30 +func StreamTargetsResponseHTML(qw422016 *qt422016.Writer, jts []jobTargetsStatuses, redirectPath string, onlyUnhealthy bool) { +//line lib/promscrape/targets_response.qtpl:30 + qw422016.N().S(`<!DOCTYPE html><style>.border{border-collapse: collapse;border: 1px solid black;}.table-row:hover{background-color: #f5f5f5;}</style><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>VictoriaMetrics Database</title></head><body><h1>Targets</h1><div id="showTargets" class="btn-group btn-group-toggle" data-toggle="buttons"><label class="btn"><input type="radio" name="targets" id="all-targets" autocomplete="off" onclick="location.href='`) +//line lib/promscrape/targets_response.qtpl:50 + qw422016.N().S(redirectPath) +//line lib/promscrape/targets_response.qtpl:50 + qw422016.N().S(`';"`) +//line lib/promscrape/targets_response.qtpl:50 + if !onlyUnhealthy { +//line lib/promscrape/targets_response.qtpl:50 + qw422016.N().S(`checked`) +//line lib/promscrape/targets_response.qtpl:50 + } +//line lib/promscrape/targets_response.qtpl:50 + qw422016.N().S(`> All</label><label class="btn"><input type="radio" name="targets" id="unhealthy-targets" autocomplete="off" onclick="location.href='`) +//line lib/promscrape/targets_response.qtpl:53 + qw422016.N().S(redirectPath) +//line lib/promscrape/targets_response.qtpl:53 + qw422016.N().S(`?show_only_unhealthy=true';"`) +//line lib/promscrape/targets_response.qtpl:53 + if onlyUnhealthy { +//line lib/promscrape/targets_response.qtpl:53 + qw422016.N().S(`checked`) +//line lib/promscrape/targets_response.qtpl:53 + } +//line lib/promscrape/targets_response.qtpl:53 + qw422016.N().S(`> Unhealthy</label><br /></div>`) +//line lib/promscrape/targets_response.qtpl:57 + for _, js := range jts { +//line lib/promscrape/targets_response.qtpl:57 + qw422016.N().S(`<div class="table-container"><h2 class="job_header danger"><a id="job-`) +//line lib/promscrape/targets_response.qtpl:60 + qw422016.N().Q(js.job) +//line lib/promscrape/targets_response.qtpl:60 + qw422016.N().S(`" >`) +//line lib/promscrape/targets_response.qtpl:60 + qw422016.N().Q(js.job) +//line lib/promscrape/targets_response.qtpl:60 + qw422016.N().S(`(`) +//line lib/promscrape/targets_response.qtpl:60 + qw422016.N().D(js.upCount) +//line lib/promscrape/targets_response.qtpl:60 + qw422016.N().S(`/`) +//line lib/promscrape/targets_response.qtpl:60 + qw422016.N().D(js.targetsTotal) +//line lib/promscrape/targets_response.qtpl:60 + qw422016.N().S(`up)</a></h2><table class="table-bordered table-hover border"><thead class="job_details border"><tr class="table-row border"><th class="border">Endpoint</th><th class="border">State</th><th class="border">Labels</th><th class="border">Last Scrape</th><th class="border">Scrape Duration</th><th class="border">Error</th></tr></thead><tbody>`) +//line lib/promscrape/targets_response.qtpl:74 + for _, ts := range js.targetsStatus { +//line lib/promscrape/targets_response.qtpl:75 + if onlyUnhealthy && ts.up { +//line lib/promscrape/targets_response.qtpl:75 + continue +//line lib/promscrape/targets_response.qtpl:75 + } +//line lib/promscrape/targets_response.qtpl:75 + qw422016.N().S(`<tr class="table-row border"><td class="endpoint border"><a href="`) +//line lib/promscrape/targets_response.qtpl:78 + qw422016.N().S(ts.endpoint) +//line lib/promscrape/targets_response.qtpl:78 + qw422016.N().S(`">`) +//line lib/promscrape/targets_response.qtpl:78 + qw422016.N().S(ts.endpoint) +//line lib/promscrape/targets_response.qtpl:78 + qw422016.N().S(`</a><br></td><td class="state border"><span class="state_indicator">`) +//line lib/promscrape/targets_response.qtpl:81 + if ts.up { +//line lib/promscrape/targets_response.qtpl:81 + qw422016.N().S(`UP`) +//line lib/promscrape/targets_response.qtpl:81 + } else { +//line lib/promscrape/targets_response.qtpl:81 + qw422016.N().S(`DOWN`) +//line lib/promscrape/targets_response.qtpl:81 + } +//line lib/promscrape/targets_response.qtpl:81 + qw422016.N().S(`</span></td><td class="labels border", title="Original`) +//line lib/promscrape/targets_response.qtpl:83 + qw422016.N().S(` `) +//line lib/promscrape/targets_response.qtpl:83 + qw422016.N().S(`labels:`) +//line lib/promscrape/targets_response.qtpl:83 + qw422016.N().S(` `) +//line lib/promscrape/targets_response.qtpl:83 + streamformatLabel(qw422016, ts.originalLabels) +//line lib/promscrape/targets_response.qtpl:83 + qw422016.N().S(`">`) +//line lib/promscrape/targets_response.qtpl:84 + streamformatLabel(qw422016, ts.labels) +//line lib/promscrape/targets_response.qtpl:84 + qw422016.N().S(`</td><td class="last-scrape border">`) +//line lib/promscrape/targets_response.qtpl:86 + qw422016.E().S(ts.lastScrapeTime.String()) +//line lib/promscrape/targets_response.qtpl:86 + qw422016.N().S(`ago</td><td class="scrape-duration border">`) +//line lib/promscrape/targets_response.qtpl:87 + qw422016.E().S(ts.scrapeDuration.String()) +//line lib/promscrape/targets_response.qtpl:87 + qw422016.N().S(`</td><td class="errors border"><span class="alert alert-danger state_indicator">`) +//line lib/promscrape/targets_response.qtpl:88 + qw422016.N().S(ts.error) +//line lib/promscrape/targets_response.qtpl:88 + qw422016.N().S(`</span></td></tr>`) +//line lib/promscrape/targets_response.qtpl:90 + } +//line lib/promscrape/targets_response.qtpl:90 + qw422016.N().S(`</tbody></table></div>`) +//line lib/promscrape/targets_response.qtpl:94 + } +//line lib/promscrape/targets_response.qtpl:94 + qw422016.N().S(`</body></html>`) +//line lib/promscrape/targets_response.qtpl:97 +} + +//line lib/promscrape/targets_response.qtpl:97 +func WriteTargetsResponseHTML(qq422016 qtio422016.Writer, jts []jobTargetsStatuses, redirectPath string, onlyUnhealthy bool) { +//line lib/promscrape/targets_response.qtpl:97 + qw422016 := qt422016.AcquireWriter(qq422016) +//line lib/promscrape/targets_response.qtpl:97 + StreamTargetsResponseHTML(qw422016, jts, redirectPath, onlyUnhealthy) +//line lib/promscrape/targets_response.qtpl:97 + qt422016.ReleaseWriter(qw422016) +//line lib/promscrape/targets_response.qtpl:97 +} + +//line lib/promscrape/targets_response.qtpl:97 +func TargetsResponseHTML(jts []jobTargetsStatuses, redirectPath string, onlyUnhealthy bool) string { +//line lib/promscrape/targets_response.qtpl:97 + qb422016 := qt422016.AcquireByteBuffer() +//line lib/promscrape/targets_response.qtpl:97 + WriteTargetsResponseHTML(qb422016, jts, redirectPath, onlyUnhealthy) +//line lib/promscrape/targets_response.qtpl:97 + qs422016 := string(qb422016.B) +//line lib/promscrape/targets_response.qtpl:97 + qt422016.ReleaseByteBuffer(qb422016) +//line lib/promscrape/targets_response.qtpl:97 + return qs422016 +//line lib/promscrape/targets_response.qtpl:97 +} + +//line lib/promscrape/targets_response.qtpl:99 +func streamformatLabel(qw422016 *qt422016.Writer, labels []prompbmarshal.Label) { +//line lib/promscrape/targets_response.qtpl:100 + for _, label := range labels { +//line lib/promscrape/targets_response.qtpl:101 + qw422016.N().S(` `) +//line lib/promscrape/targets_response.qtpl:101 + qw422016.E().S(label.Name) +//line lib/promscrape/targets_response.qtpl:101 + qw422016.N().S(`=`) +//line lib/promscrape/targets_response.qtpl:101 + qw422016.E().Q(label.Value) +//line lib/promscrape/targets_response.qtpl:101 + qw422016.N().S(` `) +//line lib/promscrape/targets_response.qtpl:102 + } +//line lib/promscrape/targets_response.qtpl:103 +} + +//line lib/promscrape/targets_response.qtpl:103 +func writeformatLabel(qq422016 qtio422016.Writer, labels []prompbmarshal.Label) { +//line lib/promscrape/targets_response.qtpl:103 + qw422016 := qt422016.AcquireWriter(qq422016) +//line lib/promscrape/targets_response.qtpl:103 + streamformatLabel(qw422016, labels) +//line lib/promscrape/targets_response.qtpl:103 + qt422016.ReleaseWriter(qw422016) +//line lib/promscrape/targets_response.qtpl:103 +} + +//line lib/promscrape/targets_response.qtpl:103 +func formatLabel(labels []prompbmarshal.Label) string { +//line lib/promscrape/targets_response.qtpl:103 + qb422016 := qt422016.AcquireByteBuffer() +//line lib/promscrape/targets_response.qtpl:103 + writeformatLabel(qb422016, labels) +//line lib/promscrape/targets_response.qtpl:103 + qs422016 := string(qb422016.B) +//line lib/promscrape/targets_response.qtpl:103 + qt422016.ReleaseByteBuffer(qb422016) +//line lib/promscrape/targets_response.qtpl:103 + return qs422016 +//line lib/promscrape/targets_response.qtpl:103 +} diff --git a/lib/promscrape/targetstatus.go b/lib/promscrape/targetstatus.go index 0a75f00e41..cd10293cfe 100644 --- a/lib/promscrape/targetstatus.go +++ b/lib/promscrape/targetstatus.go @@ -4,11 +4,14 @@ import ( "flag" "fmt" "io" + "net/http" + "path" "sort" "sync" "time" "github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime" + "github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver" "github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal" "github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel" ) @@ -19,9 +22,16 @@ var maxDroppedTargets = flag.Int("promscrape.maxDroppedTargets", 1000, "The maxi var tsmGlobal = newTargetStatusMap() -// WriteHumanReadableTargetsStatus writes human-readable status for all the scrape targets to w. -func WriteHumanReadableTargetsStatus(w io.Writer, showOriginalLabels bool) { - tsmGlobal.WriteHumanReadable(w, showOriginalLabels) +// WriteHumanReadableTargetsStatus writes human-readable status for all the scrape targets to w with given format and options. +func WriteHumanReadableTargetsStatus(w http.ResponseWriter, showOriginalLabels, showOnlyUnhealthy bool, format string) { + switch format { + case "plain": + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + tsmGlobal.WriteTargetsPlain(w, showOriginalLabels) + case "html": + w.Header().Set("Content-Type", "text/html; charset=utf-8") + tsmGlobal.WriteTargetsHTML(w, showOnlyUnhealthy) + } } // WriteAPIV1Targets writes /api/v1/targets to w according to https://prometheus.io/docs/prometheus/latest/querying/api/#targets @@ -162,64 +172,6 @@ func writeLabelsJSON(w io.Writer, labels []prompbmarshal.Label) { fmt.Fprintf(w, `}`) } -func (tsm *targetStatusMap) WriteHumanReadable(w io.Writer, showOriginalLabels bool) { - byJob := make(map[string][]targetStatus) - tsm.mu.Lock() - for _, st := range tsm.m { - job := st.sw.Job() - byJob[job] = append(byJob[job], *st) - } - tsm.mu.Unlock() - - var jss []jobStatus - for job, statuses := range byJob { - jss = append(jss, jobStatus{ - job: job, - statuses: statuses, - }) - } - sort.Slice(jss, func(i, j int) bool { - return jss[i].job < jss[j].job - }) - - for _, js := range jss { - sts := js.statuses - sort.Slice(sts, func(i, j int) bool { - return sts[i].sw.ScrapeURL < sts[j].sw.ScrapeURL - }) - ups := 0 - for _, st := range sts { - if st.up { - ups++ - } - } - fmt.Fprintf(w, "job=%q (%d/%d up)\n", js.job, ups, len(sts)) - for _, st := range sts { - state := "up" - if !st.up { - state = "down" - } - labelsStr := st.sw.LabelsString() - if showOriginalLabels { - labelsStr += ", originalLabels=" + promLabelsString(st.sw.OriginalLabels) - } - lastScrape := st.getDurationFromLastScrape() - errMsg := "" - if st.err != nil { - errMsg = st.err.Error() - } - fmt.Fprintf(w, "\tstate=%s, endpoint=%s, labels=%s, last_scrape=%.3fs ago, scrape_duration=%.3fs, error=%q\n", - state, st.sw.ScrapeURL, labelsStr, lastScrape.Seconds(), float64(st.scrapeDuration)/1000, errMsg) - } - } - fmt.Fprintf(w, "\n") -} - -type jobStatus struct { - job string - statuses []targetStatus -} - type targetStatus struct { sw ScrapeWork up bool @@ -303,3 +255,84 @@ func (dt *droppedTargets) WriteDroppedTargetsJSON(w io.Writer) { var droppedTargetsMap = &droppedTargets{ m: make(map[string]droppedTarget), } + +type jobTargetStatus struct { + up bool + endpoint string + labels []prompbmarshal.Label + originalLabels []prompbmarshal.Label + lastScrapeTime time.Duration + scrapeDuration time.Duration + error string +} + +type jobTargetsStatuses struct { + job string + upCount int + targetsTotal int + targetsStatus []jobTargetStatus +} + +func (tsm *targetStatusMap) getTargetsStatusByJob() []jobTargetsStatuses { + byJob := make(map[string][]targetStatus) + tsm.mu.Lock() + for _, st := range tsm.m { + job := st.sw.Job() + byJob[job] = append(byJob[job], *st) + } + tsm.mu.Unlock() + + var jts []jobTargetsStatuses + for job, statuses := range byJob { + sort.Slice(statuses, func(i, j int) bool { + return statuses[i].sw.ScrapeURL < statuses[j].sw.ScrapeURL + }) + ups := 0 + var targetsStatuses []jobTargetStatus + for _, ts := range statuses { + if ts.up { + ups++ + } + } + for _, st := range statuses { + errMsg := "" + if st.err != nil { + errMsg = st.err.Error() + } + targetsStatuses = append(targetsStatuses, jobTargetStatus{ + up: st.up, + endpoint: st.sw.ScrapeURL, + labels: promrelabel.FinalizeLabels(nil, st.sw.Labels), + originalLabels: st.sw.OriginalLabels, + lastScrapeTime: st.getDurationFromLastScrape(), + scrapeDuration: time.Duration(st.scrapeDuration), + error: errMsg, + }) + } + jts = append(jts, jobTargetsStatuses{ + job: job, + upCount: ups, + targetsTotal: len(statuses), + targetsStatus: targetsStatuses, + }) + } + sort.Slice(jts, func(i, j int) bool { + return jts[i].job < jts[j].job + }) + return jts +} + +// WriteTargetsHTML writes targets status grouped by job into writer w in html table, +// accepts filter to show only unhealthy targets. +func (tsm *targetStatusMap) WriteTargetsHTML(w io.Writer, showOnlyUnhealthy bool) { + jss := tsm.getTargetsStatusByJob() + targetsPath := path.Join(httpserver.GetPathPrefix(), "/targets") + WriteTargetsResponseHTML(w, jss, targetsPath, showOnlyUnhealthy) +} + +// WriteTargetsPlain writes targets grouped by job into writer w in plain text, +// accept filter to show original labels. +func (tsm *targetStatusMap) WriteTargetsPlain(w io.Writer, showOriginalLabels bool) { + jss := tsm.getTargetsStatusByJob() + WriteTargetsResponsePlain(w, jss, showOriginalLabels) +} From 069c9ade5274df5ba9115fc64c3c9abeb38d7a79 Mon Sep 17 00:00:00 2001 From: Aliaksandr Valialkin <valyala@gmail.com> Date: Mon, 14 Dec 2020 14:02:57 +0200 Subject: [PATCH 3/5] app/{vmagent,vminsert}: follow-up for ce8c2dd1f1b3c6a5aad400e2fcc360e5bfaaba7b: return `/targets` page in HTML when requested via web browser --- app/vmagent/main.go | 11 ++--------- app/vminsert/main.go | 9 +-------- docs/CHANGELOG.md | 1 + lib/promscrape/targetstatus.go | 17 ++++++++++------- 4 files changed, 14 insertions(+), 24 deletions(-) diff --git a/app/vmagent/main.go b/app/vmagent/main.go index 3c429c15c6..865f034598 100644 --- a/app/vmagent/main.go +++ b/app/vmagent/main.go @@ -5,7 +5,6 @@ import ( "fmt" "net/http" "os" - "strconv" "strings" "sync/atomic" "time" @@ -144,7 +143,7 @@ func main() { } func requestHandler(w http.ResponseWriter, r *http.Request) bool { - if r.RequestURI == "/" { + if r.URL.Path == "/" { fmt.Fprintf(w, "vmagent - see docs at https://victoriametrics.github.io/vmagent.html") return true } @@ -212,13 +211,7 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool { return true case "/targets": promscrapeTargetsRequests.Inc() - showOriginalLabels, _ := strconv.ParseBool(r.FormValue("show_original_labels")) - showOnlyUnhealthy, _ := strconv.ParseBool(r.FormValue("show_only_unhealthy")) - if accept := r.Header.Get("accept"); strings.Contains(accept, "text/html") { - promscrape.WriteHumanReadableTargetsStatus(w, showOriginalLabels, showOnlyUnhealthy, "html") - return true - } - promscrape.WriteHumanReadableTargetsStatus(w, showOriginalLabels, showOnlyUnhealthy, "plain") + promscrape.WriteHumanReadableTargetsStatus(w, r) return true case "/api/v1/targets": promscrapeAPIV1TargetsRequests.Inc() diff --git a/app/vminsert/main.go b/app/vminsert/main.go index c8f27da05a..d58c0f86fa 100644 --- a/app/vminsert/main.go +++ b/app/vminsert/main.go @@ -4,7 +4,6 @@ import ( "flag" "fmt" "net/http" - "strconv" "strings" "sync/atomic" @@ -155,13 +154,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool { return true case "/targets": promscrapeTargetsRequests.Inc() - showOriginalLabels, _ := strconv.ParseBool(r.FormValue("show_original_labels")) - showOnlyUnhealthy, _ := strconv.ParseBool(r.FormValue("show_only_unhealthy")) - if accept := r.Header.Get("accept"); strings.Contains(accept, "text/html") { - promscrape.WriteHumanReadableTargetsStatus(w, showOriginalLabels, showOnlyUnhealthy, "html") - return true - } - promscrape.WriteHumanReadableTargetsStatus(w, showOriginalLabels, showOnlyUnhealthy, "plain") + promscrape.WriteHumanReadableTargetsStatus(w, r) return true case "/api/v1/targets": promscrapeAPIV1TargetsRequests.Inc() diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index f8b88b4819..b39011b339 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -3,6 +3,7 @@ # tip * FEATURE: automatically reset response cache when samples with timestamps older than `now - search.cacheTimestampOffset` are ingested to VictoriaMetrics. This makes unnecessary disabling response cache during data backfilling or resetting it after backfilling is complete as described [in these docs](https://victoriametrics.github.io/#backfilling). This feature applies only to single-node VictoriaMetrics. It doesn't apply to cluster version of VictoriaMetrics because `vminsert` nodes don't know about `vmselect` nodes where the response cache must be reset. +* FEATURE: vmagent: return user-friendly HTML page when requesting `/targets` page from web browser. The page is returned in the old plaintext format when requesting via curl or similar tool. * FEATURE: allow multiple whitespace chars between measurements, fields and timestamp when parsing InfluxDB line protocol. Though [InfluxDB line protocol](https://docs.influxdata.com/influxdb/v1.8/write_protocols/line_protocol_tutorial/) denies multiple whitespace chars between these entities, some apps improperly put multiple whitespace chars. This workaround allows accepting data from such apps. diff --git a/lib/promscrape/targetstatus.go b/lib/promscrape/targetstatus.go index cd10293cfe..93ef68bcc7 100644 --- a/lib/promscrape/targetstatus.go +++ b/lib/promscrape/targetstatus.go @@ -7,6 +7,8 @@ import ( "net/http" "path" "sort" + "strconv" + "strings" "sync" "time" @@ -22,15 +24,16 @@ var maxDroppedTargets = flag.Int("promscrape.maxDroppedTargets", 1000, "The maxi var tsmGlobal = newTargetStatusMap() -// WriteHumanReadableTargetsStatus writes human-readable status for all the scrape targets to w with given format and options. -func WriteHumanReadableTargetsStatus(w http.ResponseWriter, showOriginalLabels, showOnlyUnhealthy bool, format string) { - switch format { - case "plain": - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - tsmGlobal.WriteTargetsPlain(w, showOriginalLabels) - case "html": +// WriteHumanReadableTargetsStatus writes human-readable status for all the scrape targets to w according to r. +func WriteHumanReadableTargetsStatus(w http.ResponseWriter, r *http.Request) { + showOriginalLabels, _ := strconv.ParseBool(r.FormValue("show_original_labels")) + showOnlyUnhealthy, _ := strconv.ParseBool(r.FormValue("show_only_unhealthy")) + if accept := r.Header.Get("Accept"); strings.Contains(accept, "text/html") { w.Header().Set("Content-Type", "text/html; charset=utf-8") tsmGlobal.WriteTargetsHTML(w, showOnlyUnhealthy) + } else { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + tsmGlobal.WriteTargetsPlain(w, showOriginalLabels) } } From f8e7f433cfa8e3f184325a84baf1b79b8fac18cd Mon Sep 17 00:00:00 2001 From: Aliaksandr Valialkin <valyala@gmail.com> Date: Mon, 14 Dec 2020 14:07:58 +0200 Subject: [PATCH 4/5] app/victoria-metrics: prettify `/` page output --- app/victoria-metrics/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/victoria-metrics/main.go b/app/victoria-metrics/main.go index 0aa5ec3f54..517c10aea7 100644 --- a/app/victoria-metrics/main.go +++ b/app/victoria-metrics/main.go @@ -86,7 +86,7 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool { if r.URL.Path == "/" { fmt.Fprintf(w, "<h2>Single-node VictoriaMetrics.</h2></br>") fmt.Fprintf(w, "See docs at <a href='https://victoriametrics.github.io/'>https://victoriametrics.github.io/</a></br>") - fmt.Fprintf(w, "usefull apis: </br>") + fmt.Fprintf(w, "Useful endpoints: </br>") writeAPIHelp(w, [][]string{ {"/targets", "discovered targets list"}, {"/api/v1/targets", "advanced information about discovered targets in JSON format"}, From ae972429c740cd3ccc7ca54f5069aa3493c9dfa0 Mon Sep 17 00:00:00 2001 From: Aliaksandr Valialkin <valyala@gmail.com> Date: Mon, 14 Dec 2020 14:19:58 +0200 Subject: [PATCH 5/5] lib/promscrape: add missing whitespace between duration and `ago` word at `/targets` page --- lib/promscrape/targets_response.qtpl | 6 +++--- lib/promscrape/targets_response.qtpl.go | 8 +++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/promscrape/targets_response.qtpl b/lib/promscrape/targets_response.qtpl index 7f3d1efe33..f1fa91f9b5 100644 --- a/lib/promscrape/targets_response.qtpl +++ b/lib/promscrape/targets_response.qtpl @@ -17,7 +17,7 @@ job={%q= js.job %}{% space %} ({%d js.upCount %}/{%d js.targetsTotal %} {% space {% space %} endpoint={%s= ts.endpoint %}, {% space %} labels={%s= labels %} {% if showOriginLabels %},{% space %} originalLabels={%s= ol %}{% endif %}, - {% space %} last_scrape={%f.3 ts.lastScrapeTime.Seconds() %}s ago, + {% space %} last_scrape={%f.3 ts.lastScrapeTime.Seconds() %}s {% space %} ago, {% space %} scrape_duration={%f.3 float64(ts.scrapeDuration.Seconds()) %}s, {% space %} error={%q= ts.error %} {% newline %} @@ -83,7 +83,7 @@ job={%q= js.job %}{% space %} ({%d js.upCount %}/{%d js.targetsTotal %} {% space <td class="labels border", title="Original {% space %} labels: {% space %} {%= formatLabel(ts.originalLabels) %}"> {%= formatLabel(ts.labels) %} </td> - <td class="last-scrape border">{%s ts.lastScrapeTime.String() %} ago</td> + <td class="last-scrape border">{%s ts.lastScrapeTime.String() %} {% space %} ago</td> <td class="scrape-duration border">{%s ts.scrapeDuration.String() %}</td> <td class="errors border"><span class="alert alert-danger state_indicator">{%s= ts.error %}</span></td> </tr> @@ -101,4 +101,4 @@ job={%q= js.job %}{% space %} ({%d js.upCount %}/{%d js.targetsTotal %} {% space {% space %} {%s label.Name %}={%q label.Value %} {% space %} {% endfor %} {% endfunc %} -{% endstripspace %} \ No newline at end of file +{% endstripspace %} diff --git a/lib/promscrape/targets_response.qtpl.go b/lib/promscrape/targets_response.qtpl.go index f5e319f065..b0472bddbf 100644 --- a/lib/promscrape/targets_response.qtpl.go +++ b/lib/promscrape/targets_response.qtpl.go @@ -102,7 +102,11 @@ func StreamTargetsResponsePlain(qw422016 *qt422016.Writer, jts []jobTargetsStatu //line lib/promscrape/targets_response.qtpl:20 qw422016.N().FPrec(ts.lastScrapeTime.Seconds(), 3) //line lib/promscrape/targets_response.qtpl:20 - qw422016.N().S(`s ago,`) + qw422016.N().S(`s`) +//line lib/promscrape/targets_response.qtpl:20 + qw422016.N().S(` `) +//line lib/promscrape/targets_response.qtpl:20 + qw422016.N().S(`ago,`) //line lib/promscrape/targets_response.qtpl:21 qw422016.N().S(` `) //line lib/promscrape/targets_response.qtpl:21 @@ -250,6 +254,8 @@ func StreamTargetsResponseHTML(qw422016 *qt422016.Writer, jts []jobTargetsStatus qw422016.N().S(`</td><td class="last-scrape border">`) //line lib/promscrape/targets_response.qtpl:86 qw422016.E().S(ts.lastScrapeTime.String()) +//line lib/promscrape/targets_response.qtpl:86 + qw422016.N().S(` `) //line lib/promscrape/targets_response.qtpl:86 qw422016.N().S(`ago</td><td class="scrape-duration border">`) //line lib/promscrape/targets_response.qtpl:87