all: follow-up after 8edb390e21

- Remove unused js bloatware from /targets page. This strips down binary size by more than 100Kb
- Add /service-discovery page for API compatibility with Prometheus
- Properly load bootstrap.min.css from /prometheus/targets
- Serve static contents for /targets page from app/vminsert instead of app/vmselect, because /targets page is served from there
This commit is contained in:
Aliaksandr Valialkin 2022-06-07 00:57:05 +03:00
parent 6f0a0e3072
commit 8608dd093c
No known key found for this signature in database
GPG key ID: A72BEC6CD3D0DED1
20 changed files with 1388 additions and 1427 deletions

View file

@ -92,7 +92,8 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool {
fmt.Fprintf(w, "Useful endpoints:</br>")
httpserver.WriteAPIHelp(w, [][2]string{
{"vmui", "Web UI"},
{"targets", "discovered targets list"},
{"targets", "status for discovered active targets"},
{"service-discovery", "labels before and after relabeling for discovered targets"},
{"api/v1/targets", "advanced information about discovered targets in JSON format"},
{"config", "-promscrape.config contents"},
{"metrics", "available service metrics"},

View file

@ -167,7 +167,8 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool {
fmt.Fprintf(w, "See docs at <a href='https://docs.victoriametrics.com/vmagent.html'>https://docs.victoriametrics.com/vmagent.html</a></br>")
fmt.Fprintf(w, "Useful endpoints:</br>")
httpserver.WriteAPIHelp(w, [][2]string{
{"targets", "discovered targets list"},
{"targets", "status for discovered active targets"},
{"service-discovery", "labels before and after relabeling for discovered targets"},
{"api/v1/targets", "advanced information about discovered targets in JSON format"},
{"config", "-promscrape.config contents"},
{"metrics", "available service metrics"},
@ -271,6 +272,10 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool {
promscrapeTargetsRequests.Inc()
promscrape.WriteHumanReadableTargetsStatus(w, r)
return true
case "/service-discovery":
promscrapeServiceDiscoveryRequests.Inc()
promscrape.WriteServiceDiscovery(w, r)
return true
case "/target_response":
promscrapeTargetResponseRequests.Inc()
if err := promscrape.WriteTargetResponse(w, r); err != nil {
@ -478,8 +483,9 @@ var (
datadogCheckRunRequests = metrics.NewCounter(`vmagent_http_requests_total{path="/datadog/api/v1/check_run", protocol="datadog"}`)
datadogIntakeRequests = metrics.NewCounter(`vmagent_http_requests_total{path="/datadog/intake/", protocol="datadog"}`)
promscrapeTargetsRequests = metrics.NewCounter(`vmagent_http_requests_total{path="/targets"}`)
promscrapeAPIV1TargetsRequests = metrics.NewCounter(`vmagent_http_requests_total{path="/api/v1/targets"}`)
promscrapeTargetsRequests = metrics.NewCounter(`vmagent_http_requests_total{path="/targets"}`)
promscrapeServiceDiscoveryRequests = metrics.NewCounter(`vmagent_http_requests_total{path="/service-discovery"}`)
promscrapeAPIV1TargetsRequests = metrics.NewCounter(`vmagent_http_requests_total{path="/api/v1/targets"}`)
promscrapeTargetResponseRequests = metrics.NewCounter(`vmagent_http_requests_total{path="/target_response"}`)
promscrapeTargetResponseErrors = metrics.NewCounter(`vmagent_http_request_errors_total{path="/target_response"}`)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -3,7 +3,7 @@
<html lang="en">
<head>
<title>vmalert{% if title != "" %} - {%s title %}{% endif %}</title>
<link href="static/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<link href="static/css/bootstrap.min.css" rel="stylesheet" />
<style>
body{
min-height: 75rem;

View file

@ -35,7 +35,7 @@ func StreamHeader(qw422016 *qt422016.Writer, title string, pages []NavItem) {
}
//line app/vmalert/tpl/header.qtpl:5
qw422016.N().S(`</title>
<link href="static/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<link href="static/css/bootstrap.min.css" rel="stylesheet" />
<style>
body{
min-height: 75rem;

View file

@ -1,6 +1,7 @@
package vminsert
import (
"embed"
"flag"
"fmt"
"net/http"
@ -55,6 +56,11 @@ var (
opentsdbhttpServer *opentsdbhttpserver.Server
)
//go:embed static
var staticFiles embed.FS
var staticServer = http.FileServer(http.FS(staticFiles))
// Init initializes vminsert.
func Init() {
relabel.Init()
@ -101,6 +107,15 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
defer requestDuration.UpdateDuration(startTime)
path := strings.Replace(r.URL.Path, "//", "/", -1)
if strings.HasPrefix(path, "/static") {
staticServer.ServeHTTP(w, r)
return true
}
if strings.HasPrefix(path, "/prometheus/static") {
r.URL.Path = strings.TrimPrefix(path, "/prometheus")
staticServer.ServeHTTP(w, r)
return true
}
switch path {
case "/prometheus/api/v1/write", "/api/v1/write":
prometheusWriteRequests.Inc()
@ -196,6 +211,10 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
promscrapeTargetsRequests.Inc()
promscrape.WriteHumanReadableTargetsStatus(w, r)
return true
case "/prometheus/service-discovery", "/service-discovery":
promscrapeServiceDiscoveryRequests.Inc()
promscrape.WriteServiceDiscovery(w, r)
return true
case "/prometheus/api/v1/targets", "/api/v1/targets":
promscrapeAPIV1TargetsRequests.Inc()
w.Header().Set("Content-Type", "application/json")
@ -296,8 +315,9 @@ var (
datadogCheckRunRequests = metrics.NewCounter(`vm_http_requests_total{path="/datadog/api/v1/check_run", protocol="datadog"}`)
datadogIntakeRequests = metrics.NewCounter(`vm_http_requests_total{path="/datadog/intake/", protocol="datadog"}`)
promscrapeTargetsRequests = metrics.NewCounter(`vm_http_requests_total{path="/targets"}`)
promscrapeAPIV1TargetsRequests = metrics.NewCounter(`vm_http_requests_total{path="/api/v1/targets"}`)
promscrapeTargetsRequests = metrics.NewCounter(`vm_http_requests_total{path="/targets"}`)
promscrapeServiceDiscoveryRequests = metrics.NewCounter(`vm_http_requests_total{path="/service-discovery"}`)
promscrapeAPIV1TargetsRequests = metrics.NewCounter(`vm_http_requests_total{path="/api/v1/targets"}`)
promscrapeTargetResponseRequests = metrics.NewCounter(`vm_http_requests_total{path="/target_response"}`)
promscrapeTargetResponseErrors = metrics.NewCounter(`vm_http_request_errors_total{path="/target_response"}`)

View file

@ -86,13 +86,7 @@ var (
//go:embed vmui
var vmuiFiles embed.FS
//go:embed static
var staticFiles embed.FS
var (
vmuiFileServer = http.FileServer(http.FS(vmuiFiles))
staticServer = http.FileServer(http.FS(staticFiles))
)
var vmuiFileServer = http.FileServer(http.FS(vmuiFiles))
// RequestHandler handles remote read API requests
func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
@ -187,11 +181,6 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
vmuiFileServer.ServeHTTP(w, r)
return true
}
if strings.HasPrefix(path, "/static") {
staticServer.ServeHTTP(w, r)
return true
}
if strings.HasPrefix(path, "/api/v1/label/") {
s := path[len("/api/v1/label/"):]
if strings.HasSuffix(s, "/values") {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -15,12 +15,12 @@ The following tip changes can be tested by building VictoriaMetrics components f
## tip
* FEATURE: adds service discovery visualisation tab for `/targets` page. It simplifies service discovery debugging. See [this PR](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/2675).
* FEATURE: allow overriding default limits for in-memory cache `indexdb/tagFilters` via flag `-storage.cacheSizeIndexDBTagFilters`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2663).
* FEATURE: add support of `lowercase` and `uppercase` relabeling actions in the same way as [Prometheus 2.36.0 does](https://github.com/prometheus/prometheus/releases/tag/v2.36.0). See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2664).
* FEATURE: support query tracing, which allows determining bottlenecks during query processing. See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#query-tracing) and [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1403).
* FEATURE: add ability to change the `indexdb` rotation timezone offset via `-retentionTimezoneOffset` command-line flag. Previously it was performed at 4am UTC time. This could lead to performance degradation in the middle of the day when VictoriaMetrics runs in time zones located too far from UTC. Thanks to @cnych for [the pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/2574).
* FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert.html): remove dependency on Internet access at [web API pages](https://docs.victoriametrics.com/vmalert.html#web). Previously the functionality and the layout of these pages was broken without Internet access. See [shis issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2594).
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): implement the `http://vmagent:8429/service-discovery` page in the same way as Prometheus does. This page shows the original labels for all the discovered targets alongside the resulting labels after the relabeling. This simplifies service discovery debugging.
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): remove dependency on Internet access at `http://vmagent:8429/targets` page. Previously the page layout was broken without Internet access. See [shis issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2594).
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add support for `kubeconfig_file` option at [kubernetes_sd_configs](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config). It may be useful for Kubernetes monitoring by `vmagent` outside Kubernetes cluster. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1464).
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): expose `/api/v1/status/config` endpoint in the same way as Prometheus does. See [these docs](https://prometheus.io/docs/prometheus/latest/querying/api/#config).

View file

@ -500,7 +500,7 @@ func (sw *scrapeWork) scrapeInternal(scrapeTimestamp, realTimestamp int64) error
// This should reduce memory usage when scraping targets which return big responses.
leveledbytebufferpool.Put(body)
}
tsmGlobal.Update(sw, sw.ScrapeGroup, up == 1, realTimestamp, int64(duration*1000), samplesScraped, err)
tsmGlobal.Update(sw, up == 1, realTimestamp, int64(duration*1000), samplesScraped, err)
return err
}
@ -603,7 +603,7 @@ func (sw *scrapeWork) scrapeStream(scrapeTimestamp, realTimestamp int64) error {
sw.storeLastScrape(sbr.body)
}
sw.finalizeLastScrape()
tsmGlobal.Update(sw, sw.ScrapeGroup, up == 1, realTimestamp, int64(duration*1000), samplesScraped, err)
tsmGlobal.Update(sw, up == 1, realTimestamp, int64(duration*1000), samplesScraped, err)
// Do not track active series in streaming mode, since this may need too big amounts of memory
// when the target exports too big number of metrics.
return err

View file

@ -1,82 +0,0 @@
{% import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
) %}
{% func ServiceDiscovery(jts []jobTargetsStatuses, emptyJobs []string, showOnlyUnhealthy bool, droppedKeyStatuses []droppedKeyStatus) %}
<div class="row mt-4">
<div class="col-12">
{% for i, js := range jts %}
{% if showOnlyUnhealthy && js.upCount == js.targetsTotal %}{% continue %}{% endif %}
<h4>
<span class="me-2">{%s js.job %}{% space %}({%d js.upCount %}/{%d js.targetsTotal %}{% space %}up)</span>
<button type="button" class="btn btn-primary btn-sm me-1"
onclick="document.querySelector('.table-discovery-{%d i %}').style.display='none'">collapse
</button>
<button type="button" class="btn btn-secondary btn-sm me-1"
onclick="document.querySelector('.table-discovery-{%d i %}').style.display='block'">expand
</button>
</h4>
<div id="table-discovery-{%d i %}" class="table-responsive table-discovery-{%d i %}">
<table class="table table-striped table-hover table-bordered table-sm">
<thead>
<tr>
<th scope="col" style="width: 50%">Discovered Labels</th>
<th scope="col" style="width: 50%">Target Labels</th>
</tr>
</thead>
<tbody class="list-{%d i %}">
{% for _, ts := range js.targetsStatus %}
{% if showOnlyUnhealthy && ts.up %}{% continue %}{% endif %}
<tr {% if !ts.up %}{%space%}class="alert alert-danger" role="alert" {% endif %}>
<td class="labels">
{%= formatLabel(ts.sw.Config.OriginalLabels) %}
</td>
<td class="labels">
{%= formatLabel(promrelabel.FinalizeLabels(nil, ts.sw.Config.Labels)) %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endfor %}
</div>
</div>
{% for i,jobName := range emptyJobs %}
<div>
<h4>
<a>{%s jobName %} (0/0 up)</a>
<button type="button" class="btn btn-primary btn-sm me-1"
onclick="document.querySelector('.table-empty-{%d i %}').style.display='none'">collapse
</button>
<button type="button" class="btn btn-secondary btn-sm me-1"
onclick="document.querySelector('.table-empty-{%d i %}').style.display='block'">expand
</button>
</h4>
<table id="table-empty-{%d i %}" class="table table-striped table-hover table-bordered table-sm table-empty-{%d i %}">
<thead>
<tr>
<th scope="col" style="width: 50%">Discovered Labels</th>
<th scope="col" style="width: 50%">Target Labels</th>
</tr>
</thead>
<tbody class="list-{%d i %}">
{% for _, status := range droppedKeyStatuses %}
{% for _, label := range status.originalLabels %}
{% if label.Value == jobName %}
<tr>
<td class="labels">
{%= formatLabel(status.originalLabels) %}
</td>
<td class="labels">
<span class="badge bg-danger">DROPPED</span>
</td>
</tr>
{% endif %}
{% endfor %}
{% endfor %}
</tbody>
</table>
</div>
{% endfor %}
{% endfunc %}

View file

@ -1,279 +0,0 @@
// Code generated by qtc from "service_discovery.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line lib/promscrape/service_discovery.qtpl:1
package promscrape
//line lib/promscrape/service_discovery.qtpl:1
import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
)
//line lib/promscrape/service_discovery.qtpl:5
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line lib/promscrape/service_discovery.qtpl:5
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line lib/promscrape/service_discovery.qtpl:5
func StreamServiceDiscovery(qw422016 *qt422016.Writer, jts []jobTargetsStatuses, emptyJobs []string, showOnlyUnhealthy bool, droppedKeyStatuses []droppedKeyStatus) {
//line lib/promscrape/service_discovery.qtpl:5
qw422016.N().S(`
<div class="row mt-4">
<div class="col-12">
`)
//line lib/promscrape/service_discovery.qtpl:8
for i, js := range jts {
//line lib/promscrape/service_discovery.qtpl:8
qw422016.N().S(`
`)
//line lib/promscrape/service_discovery.qtpl:9
if showOnlyUnhealthy && js.upCount == js.targetsTotal {
//line lib/promscrape/service_discovery.qtpl:9
continue
//line lib/promscrape/service_discovery.qtpl:9
}
//line lib/promscrape/service_discovery.qtpl:9
qw422016.N().S(`
<h4>
<span class="me-2">`)
//line lib/promscrape/service_discovery.qtpl:11
qw422016.E().S(js.job)
//line lib/promscrape/service_discovery.qtpl:11
qw422016.N().S(` `)
//line lib/promscrape/service_discovery.qtpl:11
qw422016.N().S(`(`)
//line lib/promscrape/service_discovery.qtpl:11
qw422016.N().D(js.upCount)
//line lib/promscrape/service_discovery.qtpl:11
qw422016.N().S(`/`)
//line lib/promscrape/service_discovery.qtpl:11
qw422016.N().D(js.targetsTotal)
//line lib/promscrape/service_discovery.qtpl:11
qw422016.N().S(` `)
//line lib/promscrape/service_discovery.qtpl:11
qw422016.N().S(`up)</span>
<button type="button" class="btn btn-primary btn-sm me-1"
onclick="document.querySelector('.table-discovery-`)
//line lib/promscrape/service_discovery.qtpl:13
qw422016.N().D(i)
//line lib/promscrape/service_discovery.qtpl:13
qw422016.N().S(`').style.display='none'">collapse
</button>
<button type="button" class="btn btn-secondary btn-sm me-1"
onclick="document.querySelector('.table-discovery-`)
//line lib/promscrape/service_discovery.qtpl:16
qw422016.N().D(i)
//line lib/promscrape/service_discovery.qtpl:16
qw422016.N().S(`').style.display='block'">expand
</button>
</h4>
<div id="table-discovery-`)
//line lib/promscrape/service_discovery.qtpl:19
qw422016.N().D(i)
//line lib/promscrape/service_discovery.qtpl:19
qw422016.N().S(`" class="table-responsive table-discovery-`)
//line lib/promscrape/service_discovery.qtpl:19
qw422016.N().D(i)
//line lib/promscrape/service_discovery.qtpl:19
qw422016.N().S(`">
<table class="table table-striped table-hover table-bordered table-sm">
<thead>
<tr>
<th scope="col" style="width: 50%">Discovered Labels</th>
<th scope="col" style="width: 50%">Target Labels</th>
</tr>
</thead>
<tbody class="list-`)
//line lib/promscrape/service_discovery.qtpl:27
qw422016.N().D(i)
//line lib/promscrape/service_discovery.qtpl:27
qw422016.N().S(`">
`)
//line lib/promscrape/service_discovery.qtpl:28
for _, ts := range js.targetsStatus {
//line lib/promscrape/service_discovery.qtpl:28
qw422016.N().S(`
`)
//line lib/promscrape/service_discovery.qtpl:29
if showOnlyUnhealthy && ts.up {
//line lib/promscrape/service_discovery.qtpl:29
continue
//line lib/promscrape/service_discovery.qtpl:29
}
//line lib/promscrape/service_discovery.qtpl:29
qw422016.N().S(`
<tr `)
//line lib/promscrape/service_discovery.qtpl:30
if !ts.up {
//line lib/promscrape/service_discovery.qtpl:30
qw422016.N().S(` `)
//line lib/promscrape/service_discovery.qtpl:30
qw422016.N().S(`class="alert alert-danger" role="alert" `)
//line lib/promscrape/service_discovery.qtpl:30
}
//line lib/promscrape/service_discovery.qtpl:30
qw422016.N().S(`>
<td class="labels">
`)
//line lib/promscrape/service_discovery.qtpl:32
streamformatLabel(qw422016, ts.sw.Config.OriginalLabels)
//line lib/promscrape/service_discovery.qtpl:32
qw422016.N().S(`
</td>
<td class="labels">
`)
//line lib/promscrape/service_discovery.qtpl:35
streamformatLabel(qw422016, promrelabel.FinalizeLabels(nil, ts.sw.Config.Labels))
//line lib/promscrape/service_discovery.qtpl:35
qw422016.N().S(`
</td>
</tr>
`)
//line lib/promscrape/service_discovery.qtpl:38
}
//line lib/promscrape/service_discovery.qtpl:38
qw422016.N().S(`
</tbody>
</table>
</div>
`)
//line lib/promscrape/service_discovery.qtpl:42
}
//line lib/promscrape/service_discovery.qtpl:42
qw422016.N().S(`
</div>
</div>
`)
//line lib/promscrape/service_discovery.qtpl:45
for i, jobName := range emptyJobs {
//line lib/promscrape/service_discovery.qtpl:45
qw422016.N().S(`
<div>
<h4>
<a>`)
//line lib/promscrape/service_discovery.qtpl:48
qw422016.E().S(jobName)
//line lib/promscrape/service_discovery.qtpl:48
qw422016.N().S(` (0/0 up)</a>
<button type="button" class="btn btn-primary btn-sm me-1"
onclick="document.querySelector('.table-empty-`)
//line lib/promscrape/service_discovery.qtpl:50
qw422016.N().D(i)
//line lib/promscrape/service_discovery.qtpl:50
qw422016.N().S(`').style.display='none'">collapse
</button>
<button type="button" class="btn btn-secondary btn-sm me-1"
onclick="document.querySelector('.table-empty-`)
//line lib/promscrape/service_discovery.qtpl:53
qw422016.N().D(i)
//line lib/promscrape/service_discovery.qtpl:53
qw422016.N().S(`').style.display='block'">expand
</button>
</h4>
<table id="table-empty-`)
//line lib/promscrape/service_discovery.qtpl:56
qw422016.N().D(i)
//line lib/promscrape/service_discovery.qtpl:56
qw422016.N().S(`" class="table table-striped table-hover table-bordered table-sm table-empty-`)
//line lib/promscrape/service_discovery.qtpl:56
qw422016.N().D(i)
//line lib/promscrape/service_discovery.qtpl:56
qw422016.N().S(`">
<thead>
<tr>
<th scope="col" style="width: 50%">Discovered Labels</th>
<th scope="col" style="width: 50%">Target Labels</th>
</tr>
</thead>
<tbody class="list-`)
//line lib/promscrape/service_discovery.qtpl:63
qw422016.N().D(i)
//line lib/promscrape/service_discovery.qtpl:63
qw422016.N().S(`">
`)
//line lib/promscrape/service_discovery.qtpl:64
for _, status := range droppedKeyStatuses {
//line lib/promscrape/service_discovery.qtpl:64
qw422016.N().S(`
`)
//line lib/promscrape/service_discovery.qtpl:65
for _, label := range status.originalLabels {
//line lib/promscrape/service_discovery.qtpl:65
qw422016.N().S(`
`)
//line lib/promscrape/service_discovery.qtpl:66
if label.Value == jobName {
//line lib/promscrape/service_discovery.qtpl:66
qw422016.N().S(`
<tr>
<td class="labels">
`)
//line lib/promscrape/service_discovery.qtpl:69
streamformatLabel(qw422016, status.originalLabels)
//line lib/promscrape/service_discovery.qtpl:69
qw422016.N().S(`
</td>
<td class="labels">
<span class="badge bg-danger">DROPPED</span>
</td>
</tr>
`)
//line lib/promscrape/service_discovery.qtpl:75
}
//line lib/promscrape/service_discovery.qtpl:75
qw422016.N().S(`
`)
//line lib/promscrape/service_discovery.qtpl:76
}
//line lib/promscrape/service_discovery.qtpl:76
qw422016.N().S(`
`)
//line lib/promscrape/service_discovery.qtpl:77
}
//line lib/promscrape/service_discovery.qtpl:77
qw422016.N().S(`
</tbody>
</table>
</div>
`)
//line lib/promscrape/service_discovery.qtpl:81
}
//line lib/promscrape/service_discovery.qtpl:81
qw422016.N().S(`
`)
//line lib/promscrape/service_discovery.qtpl:82
}
//line lib/promscrape/service_discovery.qtpl:82
func WriteServiceDiscovery(qq422016 qtio422016.Writer, jts []jobTargetsStatuses, emptyJobs []string, showOnlyUnhealthy bool, droppedKeyStatuses []droppedKeyStatus) {
//line lib/promscrape/service_discovery.qtpl:82
qw422016 := qt422016.AcquireWriter(qq422016)
//line lib/promscrape/service_discovery.qtpl:82
StreamServiceDiscovery(qw422016, jts, emptyJobs, showOnlyUnhealthy, droppedKeyStatuses)
//line lib/promscrape/service_discovery.qtpl:82
qt422016.ReleaseWriter(qw422016)
//line lib/promscrape/service_discovery.qtpl:82
}
//line lib/promscrape/service_discovery.qtpl:82
func ServiceDiscovery(jts []jobTargetsStatuses, emptyJobs []string, showOnlyUnhealthy bool, droppedKeyStatuses []droppedKeyStatus) string {
//line lib/promscrape/service_discovery.qtpl:82
qb422016 := qt422016.AcquireByteBuffer()
//line lib/promscrape/service_discovery.qtpl:82
WriteServiceDiscovery(qb422016, jts, emptyJobs, showOnlyUnhealthy, droppedKeyStatuses)
//line lib/promscrape/service_discovery.qtpl:82
qs422016 := string(qb422016.B)
//line lib/promscrape/service_discovery.qtpl:82
qt422016.ReleaseByteBuffer(qb422016)
//line lib/promscrape/service_discovery.qtpl:82
return qs422016
//line lib/promscrape/service_discovery.qtpl:82
}

View file

@ -1,107 +0,0 @@
{% import (
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
) %}
{% func Targets(jts []jobTargetsStatuses, emptyJobs []string, showOnlyUnhealthy bool) %}
<div class="row mt-4">
<div class="col-12">
{% for i, js := range jts %}
{% if showOnlyUnhealthy && js.upCount == js.targetsTotal %}{% continue %}{% endif %}
<div class="row mb-4">
<div class="col-12">
<h4>
<span class="me-2">{%s js.job %}{% space %}({%d js.upCount %}/{%d js.targetsTotal %}{% space %}up)</span>
<button type="button" class="btn btn-primary btn-sm me-1"
onclick="document.getElementById('table-{%d i %}').style.display='none'">collapse
</button>
<button type="button" class="btn btn-secondary btn-sm me-1"
onclick="document.getElementById('table-{%d i %}').style.display='block'">expand
</button>
</h4>
<div id="table-{%d i %}" class="table-responsive">
<table class="table table-striped table-hover table-bordered table-sm">
<thead>
<tr>
<th scope="col">Endpoint</th>
<th scope="col">State</th>
<th scope="col" title="scrape target labels">Labels</th>
<th scope="col" title="total scrapes">Scrapes</th>
<th scope="col" title="total scrape errors">Errors</th>
<th scope="col" title="the time of the last scrape">Last Scrape</th>
<th scope="col" title="the duration of the last scrape">Duration</th>
<th scope="col" title="the number of metrics scraped during the last scrape">Samples</th>
<th scope="col" title="error from the last scrape (if any)">Last error</th>
</tr>
</thead>
<tbody class="list-{%d i %}">
{% for _, ts := range js.targetsStatus %}
{% code
endpoint := ts.sw.Config.ScrapeURL
targetID := getTargetID(ts.sw)
lastScrapeTime := ts.getDurationFromLastScrape()
%}
{% if showOnlyUnhealthy && ts.up %}{% continue %}{% endif %}
<tr {% if !ts.up %}{%space%}class="alert alert-danger" role="alert" {% endif %}>
<td class="endpoint"><a href="{%s endpoint %}" target="_blank">{%s endpoint %}</a> (
<a href="target_response?id={%s targetID %}" target="_blank"
title="click to fetch target response on behalf of the scraper">response</a>
)
</td>
<td>
{% if ts.up %}
<span class="badge bg-success">UP</span>
{% else %}
<span class="badge bg-danger">DOWN</span>
{% endif %}
</td>
<td class="labels">
<div title="click to show original labels"
onclick="document.getElementById('original_labels_{%s targetID %}').style.display='block'">
{%= formatLabel(promrelabel.FinalizeLabels(nil, ts.sw.Config.Labels)) %}
</div>
<div style="display:none" id="original_labels_{%s targetID %}">
{%= formatLabel(ts.sw.Config.OriginalLabels) %}
</div>
</td>
<td>{%d ts.scrapesTotal %}</td>
<td>{%d ts.scrapesFailed %}</td>
<td>
{% if lastScrapeTime < 365*24*time.Hour %}
{%f.3 lastScrapeTime.Seconds() %}s ago
{% else %}
none
{% endif %}
<td>{%d int(ts.scrapeDuration) %}ms</td>
<td>{%d ts.samplesScraped %}</td>
<td>{% if ts.err != nil %}{%s ts.err.Error() %}{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% for _, jobName := range emptyJobs %}
<div>
<h4><a>{%s jobName %} (0/0 up)</a></h4>
<table class="table table-striped table-hover table-bordered table-sm">
<thead>
<tr>
<th scope="col">Endpoint</th>
<th scope="col">State</th>
<th scope="col">Labels</th>
<th scope="col">Last Scrape</th>
<th scope="col">Scrape Duration</th>
<th scope="col">Samples Scraped</th>
<th scope="col">Error</th>
</tr>
</thead>
</table>
</div>
{% endfor %}
{% endfunc %}

View file

@ -1,327 +0,0 @@
// Code generated by qtc from "targets.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line lib/promscrape/targets.qtpl:1
package promscrape
//line lib/promscrape/targets.qtpl:1
import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
"time"
)
//line lib/promscrape/targets.qtpl:6
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line lib/promscrape/targets.qtpl:6
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line lib/promscrape/targets.qtpl:6
func StreamTargets(qw422016 *qt422016.Writer, jts []jobTargetsStatuses, emptyJobs []string, showOnlyUnhealthy bool) {
//line lib/promscrape/targets.qtpl:6
qw422016.N().S(`
<div class="row mt-4">
<div class="col-12">
`)
//line lib/promscrape/targets.qtpl:9
for i, js := range jts {
//line lib/promscrape/targets.qtpl:9
qw422016.N().S(`
`)
//line lib/promscrape/targets.qtpl:10
if showOnlyUnhealthy && js.upCount == js.targetsTotal {
//line lib/promscrape/targets.qtpl:10
continue
//line lib/promscrape/targets.qtpl:10
}
//line lib/promscrape/targets.qtpl:10
qw422016.N().S(`
<div class="row mb-4">
<div class="col-12">
<h4>
<span class="me-2">`)
//line lib/promscrape/targets.qtpl:14
qw422016.E().S(js.job)
//line lib/promscrape/targets.qtpl:14
qw422016.N().S(` `)
//line lib/promscrape/targets.qtpl:14
qw422016.N().S(`(`)
//line lib/promscrape/targets.qtpl:14
qw422016.N().D(js.upCount)
//line lib/promscrape/targets.qtpl:14
qw422016.N().S(`/`)
//line lib/promscrape/targets.qtpl:14
qw422016.N().D(js.targetsTotal)
//line lib/promscrape/targets.qtpl:14
qw422016.N().S(` `)
//line lib/promscrape/targets.qtpl:14
qw422016.N().S(`up)</span>
<button type="button" class="btn btn-primary btn-sm me-1"
onclick="document.getElementById('table-`)
//line lib/promscrape/targets.qtpl:16
qw422016.N().D(i)
//line lib/promscrape/targets.qtpl:16
qw422016.N().S(`').style.display='none'">collapse
</button>
<button type="button" class="btn btn-secondary btn-sm me-1"
onclick="document.getElementById('table-`)
//line lib/promscrape/targets.qtpl:19
qw422016.N().D(i)
//line lib/promscrape/targets.qtpl:19
qw422016.N().S(`').style.display='block'">expand
</button>
</h4>
<div id="table-`)
//line lib/promscrape/targets.qtpl:22
qw422016.N().D(i)
//line lib/promscrape/targets.qtpl:22
qw422016.N().S(`" class="table-responsive">
<table class="table table-striped table-hover table-bordered table-sm">
<thead>
<tr>
<th scope="col">Endpoint</th>
<th scope="col">State</th>
<th scope="col" title="scrape target labels">Labels</th>
<th scope="col" title="total scrapes">Scrapes</th>
<th scope="col" title="total scrape errors">Errors</th>
<th scope="col" title="the time of the last scrape">Last Scrape</th>
<th scope="col" title="the duration of the last scrape">Duration</th>
<th scope="col" title="the number of metrics scraped during the last scrape">Samples</th>
<th scope="col" title="error from the last scrape (if any)">Last error</th>
</tr>
</thead>
<tbody class="list-`)
//line lib/promscrape/targets.qtpl:37
qw422016.N().D(i)
//line lib/promscrape/targets.qtpl:37
qw422016.N().S(`">
`)
//line lib/promscrape/targets.qtpl:38
for _, ts := range js.targetsStatus {
//line lib/promscrape/targets.qtpl:38
qw422016.N().S(`
`)
//line lib/promscrape/targets.qtpl:40
endpoint := ts.sw.Config.ScrapeURL
targetID := getTargetID(ts.sw)
lastScrapeTime := ts.getDurationFromLastScrape()
//line lib/promscrape/targets.qtpl:43
qw422016.N().S(`
`)
//line lib/promscrape/targets.qtpl:44
if showOnlyUnhealthy && ts.up {
//line lib/promscrape/targets.qtpl:44
continue
//line lib/promscrape/targets.qtpl:44
}
//line lib/promscrape/targets.qtpl:44
qw422016.N().S(`
<tr `)
//line lib/promscrape/targets.qtpl:45
if !ts.up {
//line lib/promscrape/targets.qtpl:45
qw422016.N().S(` `)
//line lib/promscrape/targets.qtpl:45
qw422016.N().S(`class="alert alert-danger" role="alert" `)
//line lib/promscrape/targets.qtpl:45
}
//line lib/promscrape/targets.qtpl:45
qw422016.N().S(`>
<td class="endpoint"><a href="`)
//line lib/promscrape/targets.qtpl:46
qw422016.E().S(endpoint)
//line lib/promscrape/targets.qtpl:46
qw422016.N().S(`" target="_blank">`)
//line lib/promscrape/targets.qtpl:46
qw422016.E().S(endpoint)
//line lib/promscrape/targets.qtpl:46
qw422016.N().S(`</a> (
<a href="target_response?id=`)
//line lib/promscrape/targets.qtpl:47
qw422016.E().S(targetID)
//line lib/promscrape/targets.qtpl:47
qw422016.N().S(`" target="_blank"
title="click to fetch target response on behalf of the scraper">response</a>
)
</td>
<td>
`)
//line lib/promscrape/targets.qtpl:52
if ts.up {
//line lib/promscrape/targets.qtpl:52
qw422016.N().S(`
<span class="badge bg-success">UP</span>
`)
//line lib/promscrape/targets.qtpl:54
} else {
//line lib/promscrape/targets.qtpl:54
qw422016.N().S(`
<span class="badge bg-danger">DOWN</span>
`)
//line lib/promscrape/targets.qtpl:56
}
//line lib/promscrape/targets.qtpl:56
qw422016.N().S(`
</td>
<td class="labels">
<div title="click to show original labels"
onclick="document.getElementById('original_labels_`)
//line lib/promscrape/targets.qtpl:60
qw422016.E().S(targetID)
//line lib/promscrape/targets.qtpl:60
qw422016.N().S(`').style.display='block'">
`)
//line lib/promscrape/targets.qtpl:61
streamformatLabel(qw422016, promrelabel.FinalizeLabels(nil, ts.sw.Config.Labels))
//line lib/promscrape/targets.qtpl:61
qw422016.N().S(`
</div>
<div style="display:none" id="original_labels_`)
//line lib/promscrape/targets.qtpl:63
qw422016.E().S(targetID)
//line lib/promscrape/targets.qtpl:63
qw422016.N().S(`">
`)
//line lib/promscrape/targets.qtpl:64
streamformatLabel(qw422016, ts.sw.Config.OriginalLabels)
//line lib/promscrape/targets.qtpl:64
qw422016.N().S(`
</div>
</td>
<td>`)
//line lib/promscrape/targets.qtpl:67
qw422016.N().D(ts.scrapesTotal)
//line lib/promscrape/targets.qtpl:67
qw422016.N().S(`</td>
<td>`)
//line lib/promscrape/targets.qtpl:68
qw422016.N().D(ts.scrapesFailed)
//line lib/promscrape/targets.qtpl:68
qw422016.N().S(`</td>
<td>
`)
//line lib/promscrape/targets.qtpl:70
if lastScrapeTime < 365*24*time.Hour {
//line lib/promscrape/targets.qtpl:70
qw422016.N().S(`
`)
//line lib/promscrape/targets.qtpl:71
qw422016.N().FPrec(lastScrapeTime.Seconds(), 3)
//line lib/promscrape/targets.qtpl:71
qw422016.N().S(`s ago
`)
//line lib/promscrape/targets.qtpl:72
} else {
//line lib/promscrape/targets.qtpl:72
qw422016.N().S(`
none
`)
//line lib/promscrape/targets.qtpl:74
}
//line lib/promscrape/targets.qtpl:74
qw422016.N().S(`
<td>`)
//line lib/promscrape/targets.qtpl:75
qw422016.N().D(int(ts.scrapeDuration))
//line lib/promscrape/targets.qtpl:75
qw422016.N().S(`ms</td>
<td>`)
//line lib/promscrape/targets.qtpl:76
qw422016.N().D(ts.samplesScraped)
//line lib/promscrape/targets.qtpl:76
qw422016.N().S(`</td>
<td>`)
//line lib/promscrape/targets.qtpl:77
if ts.err != nil {
//line lib/promscrape/targets.qtpl:77
qw422016.E().S(ts.err.Error())
//line lib/promscrape/targets.qtpl:77
}
//line lib/promscrape/targets.qtpl:77
qw422016.N().S(`</td>
</tr>
`)
//line lib/promscrape/targets.qtpl:79
}
//line lib/promscrape/targets.qtpl:79
qw422016.N().S(`
</tbody>
</table>
</div>
</div>
</div>
`)
//line lib/promscrape/targets.qtpl:85
}
//line lib/promscrape/targets.qtpl:85
qw422016.N().S(`
</div>
</div>
`)
//line lib/promscrape/targets.qtpl:89
for _, jobName := range emptyJobs {
//line lib/promscrape/targets.qtpl:89
qw422016.N().S(`
<div>
<h4><a>`)
//line lib/promscrape/targets.qtpl:91
qw422016.E().S(jobName)
//line lib/promscrape/targets.qtpl:91
qw422016.N().S(` (0/0 up)</a></h4>
<table class="table table-striped table-hover table-bordered table-sm">
<thead>
<tr>
<th scope="col">Endpoint</th>
<th scope="col">State</th>
<th scope="col">Labels</th>
<th scope="col">Last Scrape</th>
<th scope="col">Scrape Duration</th>
<th scope="col">Samples Scraped</th>
<th scope="col">Error</th>
</tr>
</thead>
</table>
</div>
`)
//line lib/promscrape/targets.qtpl:106
}
//line lib/promscrape/targets.qtpl:106
qw422016.N().S(`
`)
//line lib/promscrape/targets.qtpl:107
}
//line lib/promscrape/targets.qtpl:107
func WriteTargets(qq422016 qtio422016.Writer, jts []jobTargetsStatuses, emptyJobs []string, showOnlyUnhealthy bool) {
//line lib/promscrape/targets.qtpl:107
qw422016 := qt422016.AcquireWriter(qq422016)
//line lib/promscrape/targets.qtpl:107
StreamTargets(qw422016, jts, emptyJobs, showOnlyUnhealthy)
//line lib/promscrape/targets.qtpl:107
qt422016.ReleaseWriter(qw422016)
//line lib/promscrape/targets.qtpl:107
}
//line lib/promscrape/targets.qtpl:107
func Targets(jts []jobTargetsStatuses, emptyJobs []string, showOnlyUnhealthy bool) string {
//line lib/promscrape/targets.qtpl:107
qb422016 := qt422016.AcquireByteBuffer()
//line lib/promscrape/targets.qtpl:107
WriteTargets(qb422016, jts, emptyJobs, showOnlyUnhealthy)
//line lib/promscrape/targets.qtpl:107
qs422016 := string(qb422016.B)
//line lib/promscrape/targets.qtpl:107
qt422016.ReleaseByteBuffer(qb422016)
//line lib/promscrape/targets.qtpl:107
return qs422016
//line lib/promscrape/targets.qtpl:107
}

View file

@ -45,27 +45,25 @@ func WriteTargetResponse(w http.ResponseWriter, r *http.Request) error {
// 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"))
endpointSearch := strings.TrimSpace(r.FormValue("endpoint_search"))
labelSearch := strings.TrimSpace(r.FormValue("label_search"))
activeTab := strings.TrimSpace(r.FormValue("active_tab"))
filter := requestFilter{
showOriginalLabels: showOriginalLabels,
showOnlyUnhealthy: showOnlyUnhealthy,
endpointSearch: endpointSearch,
labelSearch: labelSearch,
activeTab: activeTab,
}
filter := getRequestFilter(r)
tsr := tsmGlobal.getTargetsStatusByJob(filter)
if accept := r.Header.Get("Accept"); strings.Contains(accept, "text/html") {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
tsmGlobal.WriteTargetsHTML(w, filter)
WriteTargetsResponseHTML(w, tsr, filter)
} else {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
tsmGlobal.WriteTargetsPlain(w, filter)
WriteTargetsResponsePlain(w, tsr, filter)
}
}
// WriteServiceDiscovery writes /service-discovery response to w similar to http://demo.robustperception.io:9090/service-discovery
func WriteServiceDiscovery(w http.ResponseWriter, r *http.Request) {
filter := getRequestFilter(r)
tsr := tsmGlobal.getTargetsStatusByJob(filter)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
WriteServiceDiscoveryResponse(w, tsr, filter)
}
// WriteAPIV1Targets writes /api/v1/targets to w according to https://prometheus.io/docs/prometheus/latest/querying/api/#targets
func WriteAPIV1Targets(w io.Writer, state string) {
if state == "" {
@ -124,7 +122,7 @@ func (tsm *targetStatusMap) Unregister(sw *scrapeWork) {
tsm.mu.Unlock()
}
func (tsm *targetStatusMap) Update(sw *scrapeWork, group string, up bool, scrapeTime, scrapeDuration int64, samplesScraped int, err error) {
func (tsm *targetStatusMap) Update(sw *scrapeWork, up bool, scrapeTime, scrapeDuration int64, samplesScraped int, err error) {
tsm.mu.Lock()
ts := tsm.m[sw]
if ts == nil {
@ -134,7 +132,6 @@ func (tsm *targetStatusMap) Update(sw *scrapeWork, group string, up bool, scrape
tsm.m[sw] = ts
}
ts.up = up
ts.scrapeGroup = group
ts.scrapeTime = scrapeTime
ts.scrapeDuration = scrapeDuration
ts.samplesScraped = samplesScraped
@ -166,8 +163,8 @@ func getTargetID(sw *scrapeWork) string {
func (tsm *targetStatusMap) StatusByGroup(group string, up bool) int {
var count int
tsm.mu.Lock()
for _, st := range tsm.m {
if st.scrapeGroup == group && st.up == up {
for _, ts := range tsm.m {
if ts.sw.ScrapeGroup == group && ts.up == up {
count++
}
}
@ -175,56 +172,48 @@ func (tsm *targetStatusMap) StatusByGroup(group string, up bool) int {
return count
}
type activeKeyStatus struct {
key string
st targetStatus
}
func (tsm *targetStatusMap) getActiveKeyStatuses() []activeKeyStatus {
func (tsm *targetStatusMap) getActiveTargetStatuses() []targetStatus {
tsm.mu.Lock()
kss := make([]activeKeyStatus, 0, len(tsm.m))
for sw, st := range tsm.m {
key := promLabelsString(sw.Config.OriginalLabels)
kss = append(kss, activeKeyStatus{
key: key,
st: *st,
})
tss := make([]targetStatus, 0, len(tsm.m))
for _, ts := range tsm.m {
tss = append(tss, *ts)
}
tsm.mu.Unlock()
sort.Slice(kss, func(i, j int) bool {
return kss[i].key < kss[j].key
// Sort discovered targets by __address__ label, so they stay in consistent order across calls
sort.Slice(tss, func(i, j int) bool {
addr1 := promrelabel.GetLabelValueByName(tss[i].sw.Config.OriginalLabels, "__address__")
addr2 := promrelabel.GetLabelValueByName(tss[j].sw.Config.OriginalLabels, "__address__")
return addr1 < addr2
})
return kss
return tss
}
// WriteActiveTargetsJSON writes `activeTargets` contents to w according to https://prometheus.io/docs/prometheus/latest/querying/api/#targets
func (tsm *targetStatusMap) WriteActiveTargetsJSON(w io.Writer) {
kss := tsm.getActiveKeyStatuses()
tss := tsm.getActiveTargetStatuses()
fmt.Fprintf(w, `[`)
for i, ks := range kss {
st := ks.st
for i, ts := range tss {
fmt.Fprintf(w, `{"discoveredLabels":`)
writeLabelsJSON(w, st.sw.Config.OriginalLabels)
writeLabelsJSON(w, ts.sw.Config.OriginalLabels)
fmt.Fprintf(w, `,"labels":`)
labelsFinalized := promrelabel.FinalizeLabels(nil, st.sw.Config.Labels)
labelsFinalized := promrelabel.FinalizeLabels(nil, ts.sw.Config.Labels)
writeLabelsJSON(w, labelsFinalized)
fmt.Fprintf(w, `,"scrapePool":%q`, st.sw.Config.Job())
fmt.Fprintf(w, `,"scrapeUrl":%q`, st.sw.Config.ScrapeURL)
fmt.Fprintf(w, `,"scrapePool":%q`, ts.sw.Config.Job())
fmt.Fprintf(w, `,"scrapeUrl":%q`, ts.sw.Config.ScrapeURL)
errMsg := ""
if st.err != nil {
errMsg = st.err.Error()
if ts.err != nil {
errMsg = ts.err.Error()
}
fmt.Fprintf(w, `,"lastError":%q`, errMsg)
fmt.Fprintf(w, `,"lastScrape":%q`, time.Unix(st.scrapeTime/1000, (st.scrapeTime%1000)*1e6).Format(time.RFC3339Nano))
fmt.Fprintf(w, `,"lastScrapeDuration":%g`, (time.Millisecond * time.Duration(st.scrapeDuration)).Seconds())
fmt.Fprintf(w, `,"lastSamplesScraped":%d`, st.samplesScraped)
fmt.Fprintf(w, `,"lastScrape":%q`, time.Unix(ts.scrapeTime/1000, (ts.scrapeTime%1000)*1e6).Format(time.RFC3339Nano))
fmt.Fprintf(w, `,"lastScrapeDuration":%g`, (time.Millisecond * time.Duration(ts.scrapeDuration)).Seconds())
fmt.Fprintf(w, `,"lastSamplesScraped":%d`, ts.samplesScraped)
state := "up"
if !st.up {
if !ts.up {
state = "down"
}
fmt.Fprintf(w, `,"health":%q}`, state)
if i+1 < len(kss) {
if i+1 < len(tss) {
fmt.Fprintf(w, `,`)
}
}
@ -245,7 +234,6 @@ func writeLabelsJSON(w io.Writer, labels []prompbmarshal.Label) {
type targetStatus struct {
sw *scrapeWork
up bool
scrapeGroup string
scrapeTime int64
scrapeDuration int64
samplesScraped int
@ -254,42 +242,35 @@ type targetStatus struct {
err error
}
func (st *targetStatus) getDurationFromLastScrape() time.Duration {
return time.Since(time.Unix(st.scrapeTime/1000, (st.scrapeTime%1000)*1e6))
func (ts *targetStatus) getDurationFromLastScrape() time.Duration {
return time.Since(time.Unix(ts.scrapeTime/1000, (ts.scrapeTime%1000)*1e6))
}
type (
droppedTargets struct {
mu sync.Mutex
m map[uint64]droppedTarget
lastCleanupTime uint64
}
droppedTarget struct {
originalLabels []prompbmarshal.Label
deadline uint64
}
droppedKeyStatus struct {
key string
originalLabels []prompbmarshal.Label
}
)
type droppedTargets struct {
mu sync.Mutex
m map[uint64]droppedTarget
lastCleanupTime uint64
}
func (dt *droppedTargets) getDroppedKeyStatuses() []droppedKeyStatus {
type droppedTarget struct {
originalLabels []prompbmarshal.Label
deadline uint64
}
func (dt *droppedTargets) getTargetsLabels() [][]prompbmarshal.Label {
dt.mu.Lock()
kss := make([]droppedKeyStatus, 0, len(dt.m))
dtls := make([][]prompbmarshal.Label, 0, len(dt.m))
for _, v := range dt.m {
key := promLabelsString(v.originalLabels)
kss = append(kss, droppedKeyStatus{
key: key,
originalLabels: v.originalLabels,
})
dtls = append(dtls, v.originalLabels)
}
dt.mu.Unlock()
sort.Slice(kss, func(i, j int) bool {
return kss[i].key < kss[j].key
// Sort discovered targets by __address__ label, so they stay in consistent order across calls
sort.Slice(dtls, func(i, j int) bool {
addr1 := promrelabel.GetLabelValueByName(dtls[i], "__address__")
addr2 := promrelabel.GetLabelValueByName(dtls[j], "__address__")
return addr1 < addr2
})
return kss
return dtls
}
func (dt *droppedTargets) Register(originalLabels []prompbmarshal.Label) {
@ -337,13 +318,13 @@ var xxhashPool = &sync.Pool{
// WriteDroppedTargetsJSON writes `droppedTargets` contents to w according to https://prometheus.io/docs/prometheus/latest/querying/api/#targets
func (dt *droppedTargets) WriteDroppedTargetsJSON(w io.Writer) {
kss := dt.getDroppedKeyStatuses()
dtls := dt.getTargetsLabels()
fmt.Fprintf(w, `[`)
for i, ks := range kss {
for i, labels := range dtls {
fmt.Fprintf(w, `{"discoveredLabels":`)
writeLabelsJSON(w, ks.originalLabels)
writeLabelsJSON(w, labels)
fmt.Fprintf(w, `}`)
if i+1 < len(kss) {
if i+1 < len(dtls) {
fmt.Fprintf(w, `,`)
}
}
@ -355,24 +336,24 @@ var droppedTargetsMap = &droppedTargets{
}
type jobTargetsStatuses struct {
job string
jobName string
upCount int
targetsTotal int
targetsStatus []targetStatus
}
func (tsm *targetStatusMap) getTargetsStatusByJob(endpointSearch, labelSearch string) ([]jobTargetsStatuses, []string, error) {
func (tsm *targetStatusMap) getTargetsStatusByJob(filter *requestFilter) *targetsStatusResult {
byJob := make(map[string][]targetStatus)
tsm.mu.Lock()
for _, st := range tsm.m {
job := st.sw.Config.jobNameOriginal
byJob[job] = append(byJob[job], *st)
for _, ts := range tsm.m {
jobName := ts.sw.Config.jobNameOriginal
byJob[jobName] = append(byJob[jobName], *ts)
}
jobNames := append([]string{}, tsm.jobNames...)
tsm.mu.Unlock()
var jts []jobTargetsStatuses
for job, statuses := range byJob {
var jts []*jobTargetsStatuses
for jobName, statuses := range byJob {
sort.Slice(statuses, func(i, j int) bool {
return statuses[i].sw.Config.ScrapeURL < statuses[j].sw.Config.ScrapeURL
})
@ -382,29 +363,38 @@ func (tsm *targetStatusMap) getTargetsStatusByJob(endpointSearch, labelSearch st
if ts.up {
ups++
}
if filter.showOnlyUnhealthy && ts.up {
continue
}
targetsStatuses = append(targetsStatuses, ts)
}
jts = append(jts, jobTargetsStatuses{
job: job,
jts = append(jts, &jobTargetsStatuses{
jobName: jobName,
upCount: ups,
targetsTotal: len(statuses),
targetsStatus: targetsStatuses,
})
}
sort.Slice(jts, func(i, j int) bool {
return jts[i].job < jts[j].job
return jts[i].jobName < jts[j].jobName
})
emptyJobs := getEmptyJobs(jts, jobNames)
var err error
jts, err = filterTargets(jts, endpointSearch, labelSearch)
if len(endpointSearch) > 0 || len(labelSearch) > 0 {
jts, err = filterTargets(jts, filter.endpointSearch, filter.labelSearch)
if len(filter.endpointSearch) > 0 || len(filter.labelSearch) > 0 {
// Do not show empty jobs if target filters are set.
emptyJobs = nil
}
return jts, emptyJobs, err
dtls := droppedTargetsMap.getTargetsLabels()
return &targetsStatusResult{
jobTargetsStatuses: jts,
droppedTargetsLabels: dtls,
emptyJobs: emptyJobs,
err: err,
}
}
func filterTargetsByEndpoint(jts []jobTargetsStatuses, searchQuery string) ([]jobTargetsStatuses, error) {
func filterTargetsByEndpoint(jts []*jobTargetsStatuses, searchQuery string) ([]*jobTargetsStatuses, error) {
if searchQuery == "" {
return jts, nil
}
@ -412,7 +402,7 @@ func filterTargetsByEndpoint(jts []jobTargetsStatuses, searchQuery string) ([]jo
if err != nil {
return nil, fmt.Errorf("cannot parse %s: %w", searchQuery, err)
}
var jtsFiltered []jobTargetsStatuses
var jtsFiltered []*jobTargetsStatuses
for _, job := range jts {
var tss []targetStatus
for _, ts := range job.targetsStatus {
@ -430,7 +420,7 @@ func filterTargetsByEndpoint(jts []jobTargetsStatuses, searchQuery string) ([]jo
return jtsFiltered, nil
}
func filterTargetsByLabels(jts []jobTargetsStatuses, searchQuery string) ([]jobTargetsStatuses, error) {
func filterTargetsByLabels(jts []*jobTargetsStatuses, searchQuery string) ([]*jobTargetsStatuses, error) {
if searchQuery == "" {
return jts, nil
}
@ -438,7 +428,7 @@ func filterTargetsByLabels(jts []jobTargetsStatuses, searchQuery string) ([]jobT
if err := ie.Parse(searchQuery); err != nil {
return nil, fmt.Errorf("cannot parse %s: %w", searchQuery, err)
}
var jtsFiltered []jobTargetsStatuses
var jtsFiltered []*jobTargetsStatuses
for _, job := range jts {
var tss []targetStatus
for _, ts := range job.targetsStatus {
@ -456,7 +446,7 @@ func filterTargetsByLabels(jts []jobTargetsStatuses, searchQuery string) ([]jobT
return jtsFiltered, nil
}
func filterTargets(jts []jobTargetsStatuses, endpointQuery, labelQuery string) ([]jobTargetsStatuses, error) {
func filterTargets(jts []*jobTargetsStatuses, endpointQuery, labelQuery string) ([]*jobTargetsStatuses, error) {
var err error
jts, err = filterTargetsByEndpoint(jts, endpointQuery)
if err != nil {
@ -469,13 +459,13 @@ func filterTargets(jts []jobTargetsStatuses, endpointQuery, labelQuery string) (
return jts, nil
}
func getEmptyJobs(jts []jobTargetsStatuses, jobNames []string) []string {
func getEmptyJobs(jts []*jobTargetsStatuses, jobNames []string) []string {
jobNamesMap := make(map[string]struct{}, len(jobNames))
for _, jobName := range jobNames {
jobNamesMap[jobName] = struct{}{}
}
for i := range jts {
delete(jobNamesMap, jts[i].job)
delete(jobNamesMap, jts[i].jobName)
}
emptyJobs := make([]string, 0, len(jobNamesMap))
for k := range jobNamesMap {
@ -485,46 +475,85 @@ func getEmptyJobs(jts []jobTargetsStatuses, jobNames []string) []string {
return emptyJobs
}
type (
requestFilter struct {
showOriginalLabels bool
showOnlyUnhealthy bool
endpointSearch string
labelSearch string
activeTab string
}
targetsStatuses struct {
jobTargetsStatuses []jobTargetsStatuses
droppedKeyStatuses []droppedKeyStatus
emptyJobs []string
err error
}
scrapeTargets struct {
requestFilter
targetsStatuses
}
)
// 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, filter requestFilter) {
droppedKeyStatuses := droppedTargetsMap.getDroppedKeyStatuses()
jss, emptyJobs, err := tsm.getTargetsStatusByJob(filter.endpointSearch, filter.labelSearch)
scrapeTargets := scrapeTargets{
requestFilter: filter,
targetsStatuses: targetsStatuses{
jobTargetsStatuses: jss,
droppedKeyStatuses: droppedKeyStatuses,
emptyJobs: emptyJobs,
err: err,
},
}
WriteTargetsResponseHTML(w, scrapeTargets)
type requestFilter struct {
showOriginalLabels bool
showOnlyUnhealthy bool
endpointSearch string
labelSearch string
}
// 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, filter requestFilter) {
jss, emptyJobs, err := tsm.getTargetsStatusByJob(filter.endpointSearch, filter.labelSearch)
WriteTargetsResponsePlain(w, jss, emptyJobs, filter.showOriginalLabels, filter.showOnlyUnhealthy, err)
func getRequestFilter(r *http.Request) *requestFilter {
showOriginalLabels, _ := strconv.ParseBool(r.FormValue("show_original_labels"))
showOnlyUnhealthy, _ := strconv.ParseBool(r.FormValue("show_only_unhealthy"))
endpointSearch := strings.TrimSpace(r.FormValue("endpoint_search"))
labelSearch := strings.TrimSpace(r.FormValue("label_search"))
return &requestFilter{
showOriginalLabels: showOriginalLabels,
showOnlyUnhealthy: showOnlyUnhealthy,
endpointSearch: endpointSearch,
labelSearch: labelSearch,
}
}
type targetsStatusResult struct {
jobTargetsStatuses []*jobTargetsStatuses
droppedTargetsLabels [][]prompbmarshal.Label
emptyJobs []string
err error
}
type targetLabels struct {
up bool
discoveredLabels []prompbmarshal.Label
labels []prompbmarshal.Label
}
type targetLabelsByJob struct {
jobName string
targets []targetLabels
activeTargets int
droppedTargets int
}
func (tsr *targetsStatusResult) getTargetLabelsByJob() []*targetLabelsByJob {
byJob := make(map[string]*targetLabelsByJob)
for _, jts := range tsr.jobTargetsStatuses {
jobName := jts.jobName
for _, ts := range jts.targetsStatus {
m := byJob[jobName]
if m == nil {
m = &targetLabelsByJob{
jobName: jobName,
}
byJob[jobName] = m
}
m.activeTargets++
m.targets = append(m.targets, targetLabels{
up: ts.up,
discoveredLabels: ts.sw.Config.OriginalLabels,
labels: ts.sw.Config.Labels,
})
}
}
for _, labels := range tsr.droppedTargetsLabels {
jobName := promrelabel.GetLabelValueByName(labels, "job")
m := byJob[jobName]
if m == nil {
m = &targetLabelsByJob{
jobName: jobName,
}
byJob[jobName] = m
}
m.droppedTargets++
m.targets = append(m.targets, targetLabels{
discoveredLabels: labels,
})
}
a := make([]*targetLabelsByJob, 0, len(byJob))
for _, tls := range byJob {
a = append(a, tls)
}
sort.Slice(a, func(i, j int) bool {
return a[i].jobName < a[j].jobName
})
return a
}

View file

@ -1,32 +1,32 @@
{% import (
"net/url"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
) %}
{% stripspace %}
{% func TargetsResponsePlain(jts []jobTargetsStatuses, emptyJobs []string, showOriginLabels, showOnlyUnhealthy bool, err error) %}
{% func TargetsResponsePlain(tsr *targetsStatusResult, filter *requestFilter) %}
{% if err != nil %}
{%s= err.Error() %}
{% if tsr.err != nil %}
{%s= tsr.err.Error() %}
{% return %}
{% endif %}
{% for _, js := range jts %}
{% if showOnlyUnhealthy && js.upCount == js.targetsTotal %}{% continue %}{% endif %}
job={%q= js.job %} ({%d js.upCount %}/{%d js.targetsTotal %}{% space %}up)
{% for _, jts := range tsr.jobTargetsStatuses %}
job={%s= jts.jobName %}{% space %}({%d jts.upCount %}/{%d jts.targetsTotal %}{% space %}up)
{% newline %}
{% for _, ts := range js.targetsStatus %}
{% if showOnlyUnhealthy && ts.up %}{% continue %}{% endif %}
{% for _, ts := range jts.targetsStatus %}
{%s= "\t" %}
state={% if ts.up %}up{% else %}down{% endif %},{% space %}
endpoint={%s= ts.sw.Config.ScrapeURL %},{% space %}
labels={%s= promLabelsString(promrelabel.FinalizeLabels(nil, ts.sw.Config.Labels)) %},{% space %}
{% if showOriginLabels %}originalLabels={%s= promLabelsString(ts.sw.Config.OriginalLabels) %},{% space %}{% endif %}
{% if filter.showOriginalLabels %}originalLabels={%s= promLabelsString(ts.sw.Config.OriginalLabels) %},{% space %}{% endif %}
scrapes_total={%d ts.scrapesTotal %},{% space %}
scrapes_failed={%d ts.scrapesFailed %},{% space %}
last_scrape={%f.3 ts.getDurationFromLastScrape().Seconds() %}s ago,{% space %}
last_scrape={%d int(ts.getDurationFromLastScrape().Milliseconds()) %}ms ago,{% space %}
scrape_duration={%d int(ts.scrapeDuration) %}ms,{% space %}
samples_scraped={%d ts.samplesScraped %},{% space %}
error={% if ts.err != nil %}{%s= ts.err.Error() %}{% endif %}
@ -34,52 +34,74 @@
{% endfor %}
{% endfor %}
{% for _, jobName := range emptyJobs %}
job={%q= jobName %} (0/0 up)
{% for _, jobName := range tsr.emptyJobs %}
job={%s= jobName %}{% space %}(0/0 up)
{% newline %}
{% endfor %}
{% endfunc %}
{% func TargetsResponseHTML(scrapeTargets scrapeTargets) %}
{% code
targetsStatuses := scrapeTargets.targetsStatuses
filter := scrapeTargets.requestFilter
if filter.activeTab == "" {
filter.activeTab="targets-tab"
}
%}
{% func TargetsResponseHTML(tsr *targetsStatusResult, filter *requestFilter) %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="static/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<title>Scrape targets</title>
<script>
function collapse_all() {
for (var i = 0; i <= {%d len(targetsStatuses.jobTargetsStatuses) %}; i++) {
["table-"+i, "table-discovery-"+i, "table-empty-"+i].forEach((id) => {
let el = document.getElementById(id);
if (el) {
el.style.display = 'none';
}
})
}
}
function expand_all() {
for (var i = 0; i <= {%d len(targetsStatuses.jobTargetsStatuses) %}; i++) {
["table-"+i, "table-discovery-"+i, "table-empty-"+i].forEach((id) => {
let el = document.getElementById(id);
if (el) {
el.style.display = 'block';
}
});
}
}
</script>
{%= commonHeader() %}
<title>Active Targets</title>
</head>
<body>
{%= navbar() %}
<div class="container-fluid">
{% if tsr.err != nil %}
{%= errorNotification(tsr.err) %}
{% endif %}
<div class="row">
<main class="col-12">
<h1>Active Targets</h1>
<hr />
{%= filtersForm(filter) %}
<hr />
{%= targetsTabs(tsr, filter, "scrapeTargets") %}
</main>
</div>
</div>
</body>
</html>
{% endfunc %}
{% func ServiceDiscoveryResponse(tsr *targetsStatusResult, filter *requestFilter) %}
<!DOCTYPE html>
<html lang="en">
<head>
{%= commonHeader() %}
<title>Discovered Targets</title>
</head>
<body>
{%= navbar() %}
<div class="container-fluid">
{% if tsr.err != nil %}
{%= errorNotification(tsr.err) %}
{% endif %}
<div class="row">
<main class="col-12">
<h1>Discovered Targets</h1>
<hr />
{%= filtersForm(filter) %}
<hr />
{%= targetsTabs(tsr, filter, "discoveredTargets") %}
</main>
</div>
</div>
</body>
</html>
{% endfunc %}
{% func commonHeader() %}
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="static/css/bootstrap.min.css" rel="stylesheet" />
{% endfunc %}
{% func navbar() %}
<div class="navbar navbar-dark bg-dark box-shadow">
<div class="d-flex justify-content-between">
<a href="#" class="navbar-brand d-flex align-items-center ms-3" title="The High Performance Open Source Time Series Database &amp; Monitoring Solution ">
@ -88,134 +110,264 @@ function expand_all() {
</a>
</div>
</div>
<div class="container-fluid">
{% if targetsStatuses.err != nil %}
{%= errorNotification(targetsStatuses.err) %}
{% endif %}
<div class="row">
<main class="col-12">
<h1>Scrape targets</h1>
<hr />
<div class="row g-3 align-items-center mb-3">
<div class="col-auto">
<button id="all-btn" type="button" class="btn{% space %}{% if !filter.showOnlyUnhealthy %}btn-secondary{% else %}btn-success{% endif %}" onclick="location.href='?{%= queryArgs(map[string]string{
"show_only_unhealthy": "false",
"endpoint_search": filter.endpointSearch,
"label_search": filter.labelSearch,
"active_tab": filter.activeTab,
}) %}'">
All
</button>
</div>
<div class="col-auto">
<button id="unhealthy-btn" type="button" class="btn{% space %}{% if filter.showOnlyUnhealthy %}btn-secondary{% else %}btn-danger{% endif %}" onclick="location.href='?{%= queryArgs(map[string]string{
"show_only_unhealthy": "true",
"endpoint_search": filter.endpointSearch,
"label_search": filter.labelSearch,
"active_tab": filter.activeTab,
}) %}'">
Unhealthy
</button>
</div>
<div class="col-auto">
<button type="button" class="btn btn-primary" onclick="collapse_all()">
Collapse all
</button>
</div>
<div class="col-auto">
<button type="button" class="btn btn-secondary" onclick="expand_all()">
Expand all
</button>
</div>
<div class="col-auto">
{% if filter.endpointSearch == "" && filter.labelSearch == "" %}
<button type="button" class="btn btn-success" onclick="document.getElementById('filters').style.display='block'">
Filter targets
</button>
{% else %}
<button type="button" class="btn btn-danger" onclick="location.href='?'">
Clear target filters
</button>
{% endif %}
</div>
</div>
<div id="filters" {% if filter.endpointSearch == "" && filter.labelSearch == "" %}style="display:none"{% endif %}>
<form class="form-horizontal">
<div class="form-group mb-3">
<label for="endpoint_search" class="col-sm-10 control-label">Endpoint filter (<a target="_blank" href="https://github.com/google/re2/wiki/Syntax">Regexp</a> is accepted)</label>
<div class="col-sm-10">
<input type="text" id="endpoint_search" name="endpoint_search"
placeholder="For example, 127.0.0.1" class="form-control" value="{%s filter.endpointSearch %}"/>
</div>
</div>
<div class="form-group mb-3">
<label for="label_search" class="col-sm-10 control-label">Labels filter (<a target="_blank" href="https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors">Arbitrary time series selectors</a> are accepted)</label>
<div class="col-sm-10">
<input type="text" id="label_search" name="label_search"
placeholder="For example, {instance=~'.+:9100'}" class="form-control" value="{%s filter.labelSearch %}"/>
</div>
</div>
<input type="hidden" name="show_only_unhealthy" value="{%v filter.showOnlyUnhealthy %}"/>
<input id="tab_input" type="hidden" name="active_tab" value="{%s filter.activeTab %}" />
<button type="submit" class="btn btn-success mb-3">Submit</button>
</form>
</div>
<hr />
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<button id="targets-tab" class="nav-link {%if filter.activeTab=="targets-tab"%} {% space %}active {%endif%}" data-bs-toggle="tab" data-bs-target="#targets" type="button" role="tab" aria-controls="home" aria-selected="true">Targets</button>
</li>
<li class="nav-item" role="presentation">
<button id="discovery-tab" class="nav-link {%if filter.activeTab=="discovery-tab"%} {% space %}active {%endif%}" data-bs-toggle="tab" data-bs-target="#discovery" type="button" role="tab" aria-controls="profile" aria-selected="false">Service Discovery</button>
</li>
</ul>
<div class="tab-content">
<div id="targets" class="tab-pane {%if filter.activeTab=="targets-tab"%} {% space %}active{%endif%}" role="tabpanel" aria-labelledby="targets-tab">
{%= Targets(targetsStatuses.jobTargetsStatuses, targetsStatuses.emptyJobs, filter.showOnlyUnhealthy) %}
</div>
<div id="discovery" class="tab-pane {%if filter.activeTab=="discovery-tab"%} {% space %}active{%endif%}" role="tabpanel" aria-labelledby="profile-tab">
{%= ServiceDiscovery(targetsStatuses.jobTargetsStatuses, targetsStatuses.emptyJobs, filter.showOnlyUnhealthy, targetsStatuses.droppedKeyStatuses) %}
</div>
</div>
</main>
</div>
</div>
<script src="static/js/jquery-3.6.0.min.js" type="text/javascript"></script>
<script src="static/js/bootstrap.bundle.min.js" type="text/javascript"></script>
<script>
(function(){
const navBtns = document.querySelectorAll(".nav-link");
const tabInput = document.getElementById("tab_input");
const unhealthyBtn = document.getElementById("unhealthy-btn");
const allBtn = document.getElementById("all-btn");
navBtns.forEach((btn) => {
if (btn) {
btn.addEventListener("click", (e) => {
if (window.history.replaceState) {
var url = new URL(window.location.href);
url.searchParams.set("active_tab", e.target.id);
tabInput.value = e.target.id;
unhealthyBtn.onclick = () => {
url.searchParams.set("show_only_unhealthy", "true");
window.location.href=url;
};
allBtn.onclick = () => {
url.searchParams.set("show_only_unhealthy", "false");
window.location.href=url;
};
window.history.replaceState({}, "", url);
}
});
}
})
})()
</script>
</body>
</html>
{% endfunc %}
{% func queryArgs(m map[string]string) %}
{% func filtersForm(filter *requestFilter) %}
<div class="row g-3 align-items-center mb-3">
<div class="col-auto">
<button id="all-btn" type="button" class="btn{% space %}{% if !filter.showOnlyUnhealthy %}btn-secondary{% else %}btn-success{% endif %}"
onclick="location.href='?{%= queryArgs(filter, map[string]string{"show_only_unhealthy": "false"}) %}'">
All
</button>
</div>
<div class="col-auto">
<button id="unhealthy-btn" type="button" class="btn{% space %}{% if filter.showOnlyUnhealthy %}btn-secondary{% else %}btn-danger{% endif %}"
onclick="location.href='?{%= queryArgs(filter, map[string]string{"show_only_unhealthy": "true"}) %}'">
Unhealthy
</button>
</div>
<div class="col-auto">
<button type="button" class="btn btn-primary" onclick="document.querySelectorAll('.scrape-job').forEach((el) => { el.style.display = 'none'; })">
Collapse all
</button>
</div>
<div class="col-auto">
<button type="button" class="btn btn-secondary" onclick="document.querySelectorAll('.scrape-job').forEach((el) => { el.style.display = 'block'; })">
Expand all
</button>
</div>
<div class="col-auto">
<button type="button" class="btn btn-success" onclick="document.getElementById('filters').style.display='block'">
Filter targets
</button>
</div>
</div>
<div id="filters" {% if filter.endpointSearch == "" && filter.labelSearch == "" %}style="display:none"{% endif %}>
<form class="form-horizontal">
<div class="form-group mb-3">
<label for="endpoint_search" class="col-sm-10 control-label">Endpoint filter (<a target="_blank" href="https://github.com/google/re2/wiki/Syntax">Regexp</a> is accepted)</label>
<div class="col-sm-10">
<input type="text" id="endpoint_search" name="endpoint_search"
placeholder="For example, 127.0.0.1" class="form-control" value="{%s filter.endpointSearch %}"/>
</div>
</div>
<div class="form-group mb-3">
<label for="label_search" class="col-sm-10 control-label">Labels filter (<a target="_blank" href="https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors">Arbitrary time series selectors</a> are accepted)</label>
<div class="col-sm-10">
<input type="text" id="label_search" name="label_search"
placeholder="For example, {instance=~'.+:9100'}" class="form-control" value="{%s filter.labelSearch %}"/>
</div>
</div>
<input type="hidden" name="show_only_unhealthy" value="{%v filter.showOnlyUnhealthy %}"/>
<input type="hidden" name="show_original_labels" value="{%v filter.showOriginalLabels %}"/>
<button type="submit" class="btn btn-success mb-3">Submit</button>
<button type="button" class="btn btn-danger mb-3" onclick="location.href='?'">Clear target filters</button>
</form>
</div>
{% endfunc %}
{% func targetsTabs(tsr *targetsStatusResult, filter *requestFilter, activeTab string) %}
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link{%if activeTab=="scrapeTargets"%}{% space %}active{%endif%}" type="button" role="tab"
onclick="location.href='targets?{%= queryArgs(filter, nil) %}'">
Active targets
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link{%if activeTab=="discoveredTargets"%}{% space %}active{%endif%}" type="button" role="tab"
onclick="location.href='service-discovery?{%= queryArgs(filter, nil) %}'">
Discovered targets
</button>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane active" role="tabpanel">
{% switch activeTab %}
{% case "scrapeTargets" %}
{%= scrapeTargets(tsr) %}
{% case "discoveredTargets" %}
{%= discoveredTargets(tsr) %}
{% endswitch %}
</div>
</div>
{% endfunc %}
{% func scrapeTargets(tsr *targetsStatusResult) %}
<div class="row mt-4">
<div class="col-12">
{% for i, jts := range tsr.jobTargetsStatuses %}
{%= scrapeJobTargets(i, jts) %}
{% endfor %}
{% for i, jobName := range tsr.emptyJobs %}
{% code
num := i + len(tsr.jobTargetsStatuses)
jts := &jobTargetsStatuses{
jobName: jobName,
}
%}
{%= scrapeJobTargets(num, jts) %}
{% endfor %}
</div>
</div>
{% endfunc %}
{% func scrapeJobTargets(num int, jts *jobTargetsStatuses) %}
<div class="row mb-4">
<div class="col-12">
<h4>
<span class="me-2">{%s jts.jobName %}{% space %}({%d jts.upCount %}/{%d jts.targetsTotal %}{% space %}up)</span>
{%= showHideScrapeJobButtons(num) %}
</h4>
<div id="scrape-job-{%d num %}" class="scrape-job table-responsive">
<table class="table table-striped table-hover table-bordered table-sm">
<thead>
<tr>
<th scope="col">Endpoint</th>
<th scope="col">State</th>
<th scope="col" title="target labels">Labels</th>
<th scope="col" title="total scrapes">Scrapes</th>
<th scope="col" title="total scrape errors">Errors</th>
<th scope="col" title="the time of the last scrape">Last Scrape</th>
<th scope="col" title="the duration of the last scrape">Duration</th>
<th scope="col" title="the number of metrics scraped during the last scrape">Samples</th>
<th scope="col" title="error from the last scrape (if any)">Last error</th>
</tr>
</thead>
<tbody>
{% for _, ts := range jts.targetsStatus %}
{% code
endpoint := ts.sw.Config.ScrapeURL
targetID := getTargetID(ts.sw)
lastScrapeDuration := ts.getDurationFromLastScrape()
%}
<tr {% if !ts.up %}{%space%}class="alert alert-danger" role="alert" {% endif %}>
<td class="endpoint">
<a href="{%s endpoint %}" target="_blank">{%s endpoint %}</a> (
<a href="target_response?id={%s targetID %}" target="_blank"
title="click to fetch target response on behalf of the scraper">response</a>
)
</td>
<td>
{% if ts.up %}
<span class="badge bg-success">UP</span>
{% else %}
<span class="badge bg-danger">DOWN</span>
{% endif %}
</td>
<td class="labels">
<div title="click to show original labels"
onclick="document.getElementById('original-labels-{%s targetID %}').style.display='block'">
{%= formatLabel(promrelabel.FinalizeLabels(nil, ts.sw.Config.Labels)) %}
</div>
<div style="display:none" id="original-labels-{%s targetID %}">
{%= formatLabel(ts.sw.Config.OriginalLabels) %}
</div>
</td>
<td>{%d ts.scrapesTotal %}</td>
<td>{%d ts.scrapesFailed %}</td>
<td>
{% if lastScrapeDuration < 365*24*time.Hour %}
{%d int(lastScrapeDuration.Milliseconds()) %}ms ago
{% else %}
none
{% endif %}
<td>{%d int(ts.scrapeDuration) %}ms</td>
<td>{%d ts.samplesScraped %}</td>
<td>{% if ts.err != nil %}{%s ts.err.Error() %}{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endfunc %}
{% func discoveredTargets(tsr *targetsStatusResult) %}
{% code tljs := tsr.getTargetLabelsByJob() %}
<div class="row mt-4">
<div class="col-12">
{% for i, tlj := range tljs %}
{%= discoveredJobTargets(i, tlj) %}
{% endfor %}
</div>
</div>
{% endfunc %}
{% func discoveredJobTargets(num int, tlj *targetLabelsByJob) %}
<h4>
<span class="me-2">{%s tlj.jobName %}{% space %}({%d tlj.activeTargets %}/{%d tlj.activeTargets+tlj.droppedTargets %}{% space %}active)</span>
{%= showHideScrapeJobButtons(num) %}
</h4>
<div id="scrape-job-{%d num %}" class="scrape-job table-responsive">
<table class="table table-striped table-hover table-bordered table-sm">
<thead>
<tr>
<th scope="col" style="width: 5%">Status</th>
<th scope="col" style="width: 65%">Discovered Labels</th>
<th scope="col" style="width: 30%">Target Labels</th>
</tr>
</thead>
<tbody>
{% for _, t := range tlj.targets %}
<tr
{% if !t.up %}
{% space %}role="alert"{% space %}
{% if len(t.labels) > 0 %}
class="alert alert-danger"
{% else %}
class="alert alert-warning"
{% endif %}
{% endif %}
>
<td>
{% if t.up %}
<span class="badge bg-success">UP</span>
{% elseif len(t.labels) > 0 %}
<span class="badge bg-danger">DOWN</span>
{% else %}
<span class="badge bg-warning">DROPPED</span>
{% endif %}
</td>
<td class="labels">
{%= formatLabel(t.discoveredLabels) %}
</td>
<td class="labels">
{%= formatLabel(promrelabel.FinalizeLabels(nil, t.labels)) %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endfunc %}
{% func showHideScrapeJobButtons(num int) %}
<button type="button" class="btn btn-primary btn-sm me-1"
onclick="document.getElementById('scrape-job-{%d num %}').style.display='none'">
collapse
</button>
<button type="button" class="btn btn-secondary btn-sm me-1"
onclick="document.getElementById('scrape-job-{%d num %}').style.display='block'">
expand
</button>
{% endfunc %}
{% func queryArgs(filter *requestFilter, override map[string]string) %}
{% code
showOnlyUnhealthy := "false"
if filter.showOnlyUnhealthy {
showOnlyUnhealthy = "true"
}
m := map[string]string{
"show_only_unhealthy": showOnlyUnhealthy,
"endpoint_search": filter.endpointSearch,
"label_search": filter.labelSearch,
}
for k, v := range override {
m[k] = v
}
qa := make(url.Values, len(m))
for k, v := range m {
qa[k] = []string{v}

File diff suppressed because it is too large Load diff