mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-23 12:31:07 +01:00
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:
parent
6f0a0e3072
commit
8608dd093c
@ -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"},
|
||||
|
@ -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
2
app/vmagent/static/js/jquery-3.6.0.min.js
vendored
2
app/vmagent/static/js/jquery-3.6.0.min.js
vendored
File diff suppressed because one or more lines are too long
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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"}`)
|
||||
|
@ -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
2
app/vmselect/static/js/jquery-3.6.0.min.js
vendored
2
app/vmselect/static/js/jquery-3.6.0.min.js
vendored
File diff suppressed because one or more lines are too long
@ -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).
|
||||
|
@ -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
|
||||
|
@ -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 %}
|
@ -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
|
||||
}
|
@ -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 %}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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 & 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
Loading…
Reference in New Issue
Block a user