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