lib/promscrape: support filtering targets via scrapePool GET param in /api/v1/targets API ()

This improves compatibility with Prometheus `/api/v1/targets` API.

https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5343

---------

Signed-off-by: hagen1778 <roman@victoriametrics.com>
Co-authored-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>

(cherry picked from commit a2ba37be68)
Signed-off-by: hagen1778 <roman@victoriametrics.com>
This commit is contained in:
Roman Khavronenko 2025-03-31 14:26:25 +02:00 committed by hagen1778
parent fc341ac05b
commit df98840167
No known key found for this signature in database
GPG key ID: E92986095E0DD614
4 changed files with 78 additions and 9 deletions
app/vmagent
docs/victoriametrics/changelog
lib/promscrape

View file

@ -443,8 +443,10 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool {
case "/prometheus/api/v1/targets", "/api/v1/targets":
promscrapeAPIV1TargetsRequests.Inc()
w.Header().Set("Content-Type", "application/json")
// https://prometheus.io/docs/prometheus/latest/querying/api/#targets
state := r.FormValue("state")
promscrape.WriteAPIV1Targets(w, state)
scrapePool := r.FormValue("scrapePool")
promscrape.WriteAPIV1Targets(w, state, scrapePool)
return true
case "/prometheus/target_response", "/target_response":
promscrapeTargetResponseRequests.Inc()

View file

@ -19,6 +19,7 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/).
## tip
* FEATURE: [dashboards/single](https://grafana.com/grafana/dashboards/10229), [dashboards/cluster](https://grafana.com/grafana/dashboards/11176): remove panel `Storage full ETA` as it could have showing incorrect predictions and result in user's confusion. See more details in [this PR](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/8492).
* FEATURE: [vmsingle](https://docs.victoriametrics.com/single-server-victoriametrics/), [vmagent](https://docs.victoriametrics.com/vmagent/): support filtering targets via `scrapePool` GET param in `/api/v1/targets` API.
* BUGFIX: [vmgateway](https://docs.victoriametrics.com/vmgateway): properly set the `Host` header when routing requests to `-write.url` and `-read.url`, which is needed for reverse proxies like Traefik.
* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert/) for [VictoriaMetrics enterprise](https://docs.victoriametrics.com/enterprise.html): properly attach tenant labels `vm_account_id` and `vm_project_id` to alerting rules when enabling `-clusterMode`. Previously, these labels were lost in alert messages to Alertmanager. Bug was introduced in [v1.112.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.112.0).

View file

@ -68,13 +68,13 @@ func WriteServiceDiscovery(w http.ResponseWriter, r *http.Request) {
}
// 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) {
func WriteAPIV1Targets(w io.Writer, state, scrapePool string) {
if state == "" {
state = "any"
}
fmt.Fprintf(w, `{"status":"success","data":{"activeTargets":`)
if state == "active" || state == "any" {
tsmGlobal.WriteActiveTargetsJSON(w)
tsmGlobal.WriteActiveTargetsJSON(w, scrapePool)
} else {
fmt.Fprintf(w, `[]`)
}
@ -254,16 +254,24 @@ func (tsm *targetStatusMap) getActiveTargetStatuses() []targetStatus {
}
// WriteActiveTargetsJSON writes `activeTargets` contents to w according to https://prometheus.io/docs/prometheus/latest/querying/api/#targets
func (tsm *targetStatusMap) WriteActiveTargetsJSON(w io.Writer) {
func (tsm *targetStatusMap) WriteActiveTargetsJSON(w io.Writer, scrapePoolFilter string) {
tss := tsm.getActiveTargetStatuses()
fmt.Fprintf(w, `[`)
for i, ts := range tss {
var needComma bool
for _, ts := range tss {
scrapePool := ts.sw.Config.jobNameOriginal
if scrapePoolFilter != "" && scrapePool != scrapePoolFilter {
continue
}
if needComma {
fmt.Fprintf(w, `,`)
}
fmt.Fprintf(w, `{"discoveredLabels":`)
writeLabelsJSON(w, ts.sw.Config.OriginalLabels)
fmt.Fprintf(w, `,"labels":`)
writeLabelsJSON(w, ts.sw.Config.Labels)
// see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5343
fmt.Fprintf(w, `,"scrapePool":%s`, stringsutil.JSONString(ts.sw.Config.jobNameOriginal))
fmt.Fprintf(w, `,"scrapePool":%s`, stringsutil.JSONString(scrapePool))
fmt.Fprintf(w, `,"scrapeUrl":%s`, stringsutil.JSONString(ts.sw.Config.ScrapeURL))
errMsg := ""
if ts.err != nil {
@ -278,9 +286,7 @@ func (tsm *targetStatusMap) WriteActiveTargetsJSON(w io.Writer) {
state = "down"
}
fmt.Fprintf(w, `,"health":%s}`, stringsutil.JSONString(state))
if i+1 < len(tss) {
fmt.Fprintf(w, `,`)
}
needComma = true
}
fmt.Fprintf(w, `]`)
}

View file

@ -0,0 +1,60 @@
package promscrape
import (
"bytes"
"encoding/json"
"reflect"
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutil"
)
func TestWriteActiveTargetsJSON(t *testing.T) {
tsm := newTargetStatusMap()
tsm.Register(&scrapeWork{
Config: &ScrapeWork{
jobNameOriginal: "foo",
OriginalLabels: promutil.NewLabelsFromMap(map[string]string{
"__address__": "host1:80",
}),
},
})
tsm.Register(&scrapeWork{
Config: &ScrapeWork{
jobNameOriginal: "bar",
OriginalLabels: promutil.NewLabelsFromMap(map[string]string{
"__address__": "host2:80",
}),
},
})
type activeTarget struct {
DiscoveredLabels map[string]string `json:"discoveredLabels"`
ScrapePool string `json:"scrapePool"`
}
f := func(scrapePoolFilter string, exp []activeTarget) {
t.Helper()
b := &bytes.Buffer{}
tsm.WriteActiveTargetsJSON(b, scrapePoolFilter)
var got []activeTarget
if err := json.Unmarshal(b.Bytes(), &got); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(got, exp) {
t.Fatalf("unexpected response; \ngot\n %s; \nwant\n %s", got, exp)
}
}
f("", []activeTarget{
{ScrapePool: "foo", DiscoveredLabels: map[string]string{"__address__": "host1:80"}},
{ScrapePool: "bar", DiscoveredLabels: map[string]string{"__address__": "host2:80"}},
})
f("foo", []activeTarget{
{ScrapePool: "foo", DiscoveredLabels: map[string]string{"__address__": "host1:80"}},
})
f("bar", []activeTarget{
{ScrapePool: "bar", DiscoveredLabels: map[string]string{"__address__": "host2:80"}},
})
f("unknown", []activeTarget{})
}