mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-26 06:04:05 -08:00
scrape,api: provide per-target metric metadata
This adds a per-target cache of scraped metadata. The metadata is only available for the lifecycle of the attached target. An API endpoint allows to select metadata by metric name and a label selection of targets. Signed-off-by: Fabian Reinartz <freinartz@google.com>
This commit is contained in:
parent
0e7112330e
commit
ad4c33c1ff
|
@ -329,7 +329,7 @@ Both the active and dropped targets are part of the response.
|
||||||
```json
|
```json
|
||||||
$ curl http://localhost:9090/api/v1/targets
|
$ curl http://localhost:9090/api/v1/targets
|
||||||
{
|
{
|
||||||
"status": "success", [3/11]
|
"status": "success",
|
||||||
"data": {
|
"data": {
|
||||||
"activeTargets": [
|
"activeTargets": [
|
||||||
{
|
{
|
||||||
|
@ -363,6 +363,85 @@ $ curl http://localhost:9090/api/v1/targets
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Target metadata
|
||||||
|
|
||||||
|
The following endpoint returns metadata about metrics currently scraped by targets.
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/targets/metadata
|
||||||
|
```
|
||||||
|
|
||||||
|
URL query parameters:
|
||||||
|
|
||||||
|
- `match=<series_selector>`: A selector that matches targets by label matchers. If a metric name is provided, only metadata for that metric name is returned.
|
||||||
|
- `limit=<number>`: Maximum number of targets to match.
|
||||||
|
|
||||||
|
The `data` section of the query result consists of a list of objects that
|
||||||
|
contain metric metadata and the target label set.
|
||||||
|
|
||||||
|
The following example returns all metadata entries for the `go_goroutines` metric
|
||||||
|
from the first two targets with label `job="prometheus"`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
curl -G http://localhost:9091/api/v1/targets/metadata \
|
||||||
|
--data-urlencode 'match=go_goroutines{job="prometheus"}' \
|
||||||
|
--data-urlencode 'limit=2'
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"target": {
|
||||||
|
"instance": "127.0.0.1:9090",
|
||||||
|
"job": "prometheus"
|
||||||
|
},
|
||||||
|
"type": "gauge",
|
||||||
|
"help": "Number of goroutines that currently exist."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target": {
|
||||||
|
"instance": "127.0.0.1:9091",
|
||||||
|
"job": "prometheus"
|
||||||
|
},
|
||||||
|
"type": "gauge",
|
||||||
|
"help": "Number of goroutines that currently exist."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The following example returns metadata for all metrics for the target with
|
||||||
|
label `instance="127.0.0.1:9090`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
curl -G http://localhost:9091/api/v1/targets/metadata \
|
||||||
|
--data-urlencode 'match={instance="127.0.0.1:9090"}'
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"data": [
|
||||||
|
// ...
|
||||||
|
{
|
||||||
|
"target": {
|
||||||
|
"instance": "127.0.0.1:9090",
|
||||||
|
"job": "prometheus"
|
||||||
|
},
|
||||||
|
"metric": "prometheus_treecache_zookeeper_failures_total",
|
||||||
|
"type": "counter",
|
||||||
|
"help": "The total number of ZooKeeper failures."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target": {
|
||||||
|
"instance": "127.0.0.1:9090",
|
||||||
|
"job": "prometheus"
|
||||||
|
},
|
||||||
|
"metric": "prometheus_tsdb_reloads_total",
|
||||||
|
"type": "counter",
|
||||||
|
"help": "Number of times the database reloaded block data from disk."
|
||||||
|
},
|
||||||
|
// ...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Alertmanagers
|
## Alertmanagers
|
||||||
|
|
||||||
The following endpoint returns an overview of the current state of the
|
The following endpoint returns an overview of the current state of the
|
||||||
|
|
|
@ -33,7 +33,6 @@ type Appendable interface {
|
||||||
|
|
||||||
// NewManager is the Manager constructor
|
// NewManager is the Manager constructor
|
||||||
func NewManager(logger log.Logger, app Appendable) *Manager {
|
func NewManager(logger log.Logger, app Appendable) *Manager {
|
||||||
|
|
||||||
return &Manager{
|
return &Manager{
|
||||||
append: app,
|
append: app,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
|
125
scrape/scrape.go
125
scrape/scrape.go
|
@ -161,6 +161,10 @@ func newScrapePool(cfg *config.ScrapeConfig, app Appendable, logger log.Logger)
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
sp.newLoop = func(t *Target, s scraper, limit int, honor bool, mrc []*config.RelabelConfig) loop {
|
sp.newLoop = func(t *Target, s scraper, limit int, honor bool, mrc []*config.RelabelConfig) loop {
|
||||||
|
// Update the targets retrieval function for metadata to a new scrape cache.
|
||||||
|
cache := newScrapeCache()
|
||||||
|
t.setMetadataStore(cache)
|
||||||
|
|
||||||
return newScrapeLoop(
|
return newScrapeLoop(
|
||||||
ctx,
|
ctx,
|
||||||
s,
|
s,
|
||||||
|
@ -175,6 +179,7 @@ func newScrapePool(cfg *config.ScrapeConfig, app Appendable, logger log.Logger)
|
||||||
}
|
}
|
||||||
return appender(app, limit)
|
return appender(app, limit)
|
||||||
},
|
},
|
||||||
|
cache,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -523,43 +528,62 @@ type scrapeCache struct {
|
||||||
|
|
||||||
// Parsed string to an entry with information about the actual label set
|
// Parsed string to an entry with information about the actual label set
|
||||||
// and its storage reference.
|
// and its storage reference.
|
||||||
entries map[string]*cacheEntry
|
series map[string]*cacheEntry
|
||||||
|
|
||||||
// Cache of dropped metric strings and their iteration. The iteration must
|
// Cache of dropped metric strings and their iteration. The iteration must
|
||||||
// be a pointer so we can update it without setting a new entry with an unsafe
|
// be a pointer so we can update it without setting a new entry with an unsafe
|
||||||
// string in addDropped().
|
// string in addDropped().
|
||||||
dropped map[string]*uint64
|
droppedSeries map[string]*uint64
|
||||||
|
|
||||||
// seriesCur and seriesPrev store the labels of series that were seen
|
// seriesCur and seriesPrev store the labels of series that were seen
|
||||||
// in the current and previous scrape.
|
// in the current and previous scrape.
|
||||||
// We hold two maps and swap them out to save allocations.
|
// We hold two maps and swap them out to save allocations.
|
||||||
seriesCur map[uint64]labels.Labels
|
seriesCur map[uint64]labels.Labels
|
||||||
seriesPrev map[uint64]labels.Labels
|
seriesPrev map[uint64]labels.Labels
|
||||||
|
|
||||||
|
metaMtx sync.Mutex
|
||||||
|
metadata map[string]*metaEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
// metaEntry holds meta information about a metric.
|
||||||
|
type metaEntry struct {
|
||||||
|
lastIter uint64 // last scrape iteration the entry was observed
|
||||||
|
typ textparse.MetricType
|
||||||
|
help string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newScrapeCache() *scrapeCache {
|
func newScrapeCache() *scrapeCache {
|
||||||
return &scrapeCache{
|
return &scrapeCache{
|
||||||
entries: map[string]*cacheEntry{},
|
series: map[string]*cacheEntry{},
|
||||||
dropped: map[string]*uint64{},
|
droppedSeries: map[string]*uint64{},
|
||||||
seriesCur: map[uint64]labels.Labels{},
|
seriesCur: map[uint64]labels.Labels{},
|
||||||
seriesPrev: map[uint64]labels.Labels{},
|
seriesPrev: map[uint64]labels.Labels{},
|
||||||
|
metadata: map[string]*metaEntry{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *scrapeCache) iterDone() {
|
func (c *scrapeCache) iterDone() {
|
||||||
// refCache and lsetCache may grow over time through series churn
|
// All caches may grow over time through series churn
|
||||||
// or multiple string representations of the same metric. Clean up entries
|
// or multiple string representations of the same metric. Clean up entries
|
||||||
// that haven't appeared in the last scrape.
|
// that haven't appeared in the last scrape.
|
||||||
for s, e := range c.entries {
|
for s, e := range c.series {
|
||||||
if c.iter-e.lastIter > 2 {
|
if c.iter-e.lastIter > 2 {
|
||||||
delete(c.entries, s)
|
delete(c.series, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for s, iter := range c.dropped {
|
for s, iter := range c.droppedSeries {
|
||||||
if c.iter-*iter > 2 {
|
if c.iter-*iter > 2 {
|
||||||
delete(c.dropped, s)
|
delete(c.droppedSeries, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
c.metaMtx.Lock()
|
||||||
|
for m, e := range c.metadata {
|
||||||
|
// Keep metadata around for 10 scrapes after its metric disappeared.
|
||||||
|
if c.iter-e.lastIter > 10 {
|
||||||
|
delete(c.metadata, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.metaMtx.Unlock()
|
||||||
|
|
||||||
// Swap current and previous series.
|
// Swap current and previous series.
|
||||||
c.seriesPrev, c.seriesCur = c.seriesCur, c.seriesPrev
|
c.seriesPrev, c.seriesCur = c.seriesCur, c.seriesPrev
|
||||||
|
@ -573,7 +597,7 @@ func (c *scrapeCache) iterDone() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *scrapeCache) get(met string) (*cacheEntry, bool) {
|
func (c *scrapeCache) get(met string) (*cacheEntry, bool) {
|
||||||
e, ok := c.entries[met]
|
e, ok := c.series[met]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
@ -585,16 +609,16 @@ func (c *scrapeCache) addRef(met string, ref uint64, lset labels.Labels, hash ui
|
||||||
if ref == 0 {
|
if ref == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.entries[met] = &cacheEntry{ref: ref, lastIter: c.iter, lset: lset, hash: hash}
|
c.series[met] = &cacheEntry{ref: ref, lastIter: c.iter, lset: lset, hash: hash}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *scrapeCache) addDropped(met string) {
|
func (c *scrapeCache) addDropped(met string) {
|
||||||
iter := c.iter
|
iter := c.iter
|
||||||
c.dropped[met] = &iter
|
c.droppedSeries[met] = &iter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *scrapeCache) getDropped(met string) bool {
|
func (c *scrapeCache) getDropped(met string) bool {
|
||||||
iterp, ok := c.dropped[met]
|
iterp, ok := c.droppedSeries[met]
|
||||||
if ok {
|
if ok {
|
||||||
*iterp = c.iter
|
*iterp = c.iter
|
||||||
}
|
}
|
||||||
|
@ -615,6 +639,65 @@ func (c *scrapeCache) forEachStale(f func(labels.Labels) bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *scrapeCache) setType(metric []byte, t textparse.MetricType) {
|
||||||
|
c.metaMtx.Lock()
|
||||||
|
|
||||||
|
e, ok := c.metadata[yoloString(metric)]
|
||||||
|
if !ok {
|
||||||
|
e = &metaEntry{typ: textparse.MetricTypeUntyped}
|
||||||
|
c.metadata[string(metric)] = e
|
||||||
|
}
|
||||||
|
e.typ = t
|
||||||
|
e.lastIter = c.iter
|
||||||
|
|
||||||
|
c.metaMtx.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *scrapeCache) setHelp(metric, help []byte) {
|
||||||
|
c.metaMtx.Lock()
|
||||||
|
|
||||||
|
e, ok := c.metadata[yoloString(metric)]
|
||||||
|
if !ok {
|
||||||
|
e = &metaEntry{typ: textparse.MetricTypeUntyped}
|
||||||
|
c.metadata[string(metric)] = e
|
||||||
|
}
|
||||||
|
if e.help != yoloString(help) {
|
||||||
|
e.help = string(help)
|
||||||
|
}
|
||||||
|
e.lastIter = c.iter
|
||||||
|
|
||||||
|
c.metaMtx.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *scrapeCache) getMetadata(metric string) (MetricMetadata, bool) {
|
||||||
|
c.metaMtx.Lock()
|
||||||
|
defer c.metaMtx.Unlock()
|
||||||
|
|
||||||
|
m, ok := c.metadata[metric]
|
||||||
|
if !ok {
|
||||||
|
return MetricMetadata{}, false
|
||||||
|
}
|
||||||
|
return MetricMetadata{
|
||||||
|
Metric: metric,
|
||||||
|
Type: m.typ,
|
||||||
|
Help: m.help,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *scrapeCache) listMetadata() (res []MetricMetadata) {
|
||||||
|
c.metaMtx.Lock()
|
||||||
|
defer c.metaMtx.Unlock()
|
||||||
|
|
||||||
|
for m, e := range c.metadata {
|
||||||
|
res = append(res, MetricMetadata{
|
||||||
|
Metric: m,
|
||||||
|
Type: e.typ,
|
||||||
|
Help: e.help,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
func newScrapeLoop(ctx context.Context,
|
func newScrapeLoop(ctx context.Context,
|
||||||
sc scraper,
|
sc scraper,
|
||||||
l log.Logger,
|
l log.Logger,
|
||||||
|
@ -622,6 +705,7 @@ func newScrapeLoop(ctx context.Context,
|
||||||
sampleMutator labelsMutator,
|
sampleMutator labelsMutator,
|
||||||
reportSampleMutator labelsMutator,
|
reportSampleMutator labelsMutator,
|
||||||
appender func() storage.Appender,
|
appender func() storage.Appender,
|
||||||
|
cache *scrapeCache,
|
||||||
) *scrapeLoop {
|
) *scrapeLoop {
|
||||||
if l == nil {
|
if l == nil {
|
||||||
l = log.NewNopLogger()
|
l = log.NewNopLogger()
|
||||||
|
@ -629,10 +713,13 @@ func newScrapeLoop(ctx context.Context,
|
||||||
if buffers == nil {
|
if buffers == nil {
|
||||||
buffers = pool.New(1e3, 1e6, 3, func(sz int) interface{} { return make([]byte, 0, sz) })
|
buffers = pool.New(1e3, 1e6, 3, func(sz int) interface{} { return make([]byte, 0, sz) })
|
||||||
}
|
}
|
||||||
|
if cache == nil {
|
||||||
|
cache = newScrapeCache()
|
||||||
|
}
|
||||||
sl := &scrapeLoop{
|
sl := &scrapeLoop{
|
||||||
scraper: sc,
|
scraper: sc,
|
||||||
buffers: buffers,
|
buffers: buffers,
|
||||||
cache: newScrapeCache(),
|
cache: cache,
|
||||||
appender: appender,
|
appender: appender,
|
||||||
sampleMutator: sampleMutator,
|
sampleMutator: sampleMutator,
|
||||||
reportSampleMutator: reportSampleMutator,
|
reportSampleMutator: reportSampleMutator,
|
||||||
|
@ -838,8 +925,16 @@ loop:
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if et != textparse.EntrySeries {
|
switch et {
|
||||||
|
case textparse.EntryType:
|
||||||
|
sl.cache.setType(p.Type())
|
||||||
continue
|
continue
|
||||||
|
case textparse.EntryHelp:
|
||||||
|
sl.cache.setHelp(p.Help())
|
||||||
|
continue
|
||||||
|
case textparse.EntryComment:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
total++
|
total++
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ import (
|
||||||
"github.com/prometheus/prometheus/config"
|
"github.com/prometheus/prometheus/config"
|
||||||
"github.com/prometheus/prometheus/discovery/targetgroup"
|
"github.com/prometheus/prometheus/discovery/targetgroup"
|
||||||
"github.com/prometheus/prometheus/pkg/labels"
|
"github.com/prometheus/prometheus/pkg/labels"
|
||||||
|
"github.com/prometheus/prometheus/pkg/textparse"
|
||||||
"github.com/prometheus/prometheus/pkg/timestamp"
|
"github.com/prometheus/prometheus/pkg/timestamp"
|
||||||
"github.com/prometheus/prometheus/pkg/value"
|
"github.com/prometheus/prometheus/pkg/value"
|
||||||
"github.com/prometheus/prometheus/storage"
|
"github.com/prometheus/prometheus/storage"
|
||||||
|
@ -306,7 +307,7 @@ func TestScrapePoolAppender(t *testing.T) {
|
||||||
app := &nopAppendable{}
|
app := &nopAppendable{}
|
||||||
sp := newScrapePool(cfg, app, nil)
|
sp := newScrapePool(cfg, app, nil)
|
||||||
|
|
||||||
loop := sp.newLoop(nil, nil, 0, false, nil)
|
loop := sp.newLoop(&Target{}, nil, 0, false, nil)
|
||||||
appl, ok := loop.(*scrapeLoop)
|
appl, ok := loop.(*scrapeLoop)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("Expected scrapeLoop but got %T", loop)
|
t.Fatalf("Expected scrapeLoop but got %T", loop)
|
||||||
|
@ -321,7 +322,7 @@ func TestScrapePoolAppender(t *testing.T) {
|
||||||
t.Fatalf("Expected base appender but got %T", tl.Appender)
|
t.Fatalf("Expected base appender but got %T", tl.Appender)
|
||||||
}
|
}
|
||||||
|
|
||||||
loop = sp.newLoop(nil, nil, 100, false, nil)
|
loop = sp.newLoop(&Target{}, nil, 100, false, nil)
|
||||||
appl, ok = loop.(*scrapeLoop)
|
appl, ok = loop.(*scrapeLoop)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("Expected scrapeLoop but got %T", loop)
|
t.Fatalf("Expected scrapeLoop but got %T", loop)
|
||||||
|
@ -387,7 +388,7 @@ func TestScrapeLoopStopBeforeRun(t *testing.T) {
|
||||||
nil, nil,
|
nil, nil,
|
||||||
nopMutator,
|
nopMutator,
|
||||||
nopMutator,
|
nopMutator,
|
||||||
nil,
|
nil, nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
// The scrape pool synchronizes on stopping scrape loops. However, new scrape
|
// The scrape pool synchronizes on stopping scrape loops. However, new scrape
|
||||||
|
@ -450,6 +451,7 @@ func TestScrapeLoopStop(t *testing.T) {
|
||||||
nopMutator,
|
nopMutator,
|
||||||
nopMutator,
|
nopMutator,
|
||||||
app,
|
app,
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Terminate loop after 2 scrapes.
|
// Terminate loop after 2 scrapes.
|
||||||
|
@ -514,6 +516,7 @@ func TestScrapeLoopRun(t *testing.T) {
|
||||||
nopMutator,
|
nopMutator,
|
||||||
nopMutator,
|
nopMutator,
|
||||||
app,
|
app,
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
// The loop must terminate during the initial offset if the context
|
// The loop must terminate during the initial offset if the context
|
||||||
|
@ -558,6 +561,7 @@ func TestScrapeLoopRun(t *testing.T) {
|
||||||
nopMutator,
|
nopMutator,
|
||||||
nopMutator,
|
nopMutator,
|
||||||
app,
|
app,
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -590,6 +594,51 @@ func TestScrapeLoopRun(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestScrapeLoopMetadata(t *testing.T) {
|
||||||
|
var (
|
||||||
|
signal = make(chan struct{})
|
||||||
|
scraper = &testScraper{}
|
||||||
|
cache = newScrapeCache()
|
||||||
|
)
|
||||||
|
defer close(signal)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
sl := newScrapeLoop(ctx,
|
||||||
|
scraper,
|
||||||
|
nil, nil,
|
||||||
|
nopMutator,
|
||||||
|
nopMutator,
|
||||||
|
func() storage.Appender { return nopAppender{} },
|
||||||
|
cache,
|
||||||
|
)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
total, _, err := sl.append([]byte(`
|
||||||
|
# TYPE test_metric counter
|
||||||
|
# HELP test_metric some help text
|
||||||
|
# other comment
|
||||||
|
test_metric 1
|
||||||
|
# TYPE test_metric_no_help gauge
|
||||||
|
# HELP test_metric_no_type other help text`), time.Now())
|
||||||
|
testutil.Ok(t, err)
|
||||||
|
testutil.Equals(t, 1, total)
|
||||||
|
|
||||||
|
md, ok := cache.getMetadata("test_metric")
|
||||||
|
testutil.Assert(t, ok, "expected metadata to be present")
|
||||||
|
testutil.Assert(t, textparse.MetricTypeCounter == md.Type, "unexpected metric type")
|
||||||
|
testutil.Equals(t, "some help text", md.Help)
|
||||||
|
|
||||||
|
md, ok = cache.getMetadata("test_metric_no_help")
|
||||||
|
testutil.Assert(t, ok, "expected metadata to be present")
|
||||||
|
testutil.Assert(t, textparse.MetricTypeGauge == md.Type, "unexpected metric type")
|
||||||
|
testutil.Equals(t, "", md.Help)
|
||||||
|
|
||||||
|
md, ok = cache.getMetadata("test_metric_no_type")
|
||||||
|
testutil.Assert(t, ok, "expected metadata to be present")
|
||||||
|
testutil.Assert(t, textparse.MetricTypeUntyped == md.Type, "unexpected metric type")
|
||||||
|
testutil.Equals(t, "other help text", md.Help)
|
||||||
|
}
|
||||||
|
|
||||||
func TestScrapeLoopRunCreatesStaleMarkersOnFailedScrape(t *testing.T) {
|
func TestScrapeLoopRunCreatesStaleMarkersOnFailedScrape(t *testing.T) {
|
||||||
appender := &collectResultAppender{}
|
appender := &collectResultAppender{}
|
||||||
var (
|
var (
|
||||||
|
@ -606,6 +655,7 @@ func TestScrapeLoopRunCreatesStaleMarkersOnFailedScrape(t *testing.T) {
|
||||||
nopMutator,
|
nopMutator,
|
||||||
nopMutator,
|
nopMutator,
|
||||||
app,
|
app,
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
// Succeed once, several failures, then stop.
|
// Succeed once, several failures, then stop.
|
||||||
numScrapes := 0
|
numScrapes := 0
|
||||||
|
@ -663,6 +713,7 @@ func TestScrapeLoopRunCreatesStaleMarkersOnParseFailure(t *testing.T) {
|
||||||
nopMutator,
|
nopMutator,
|
||||||
nopMutator,
|
nopMutator,
|
||||||
app,
|
app,
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Succeed once, several failures, then stop.
|
// Succeed once, several failures, then stop.
|
||||||
|
@ -766,6 +817,7 @@ func TestScrapeLoopAppend(t *testing.T) {
|
||||||
return mutateReportSampleLabels(l, discoveryLabels)
|
return mutateReportSampleLabels(l, discoveryLabels)
|
||||||
},
|
},
|
||||||
func() storage.Appender { return app },
|
func() storage.Appender { return app },
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
@ -804,6 +856,7 @@ func TestScrapeLoopAppendSampleLimit(t *testing.T) {
|
||||||
nopMutator,
|
nopMutator,
|
||||||
nopMutator,
|
nopMutator,
|
||||||
func() storage.Appender { return app },
|
func() storage.Appender { return app },
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Get the value of the Counter before performing the append.
|
// Get the value of the Counter before performing the append.
|
||||||
|
@ -863,6 +916,7 @@ func TestScrapeLoop_ChangingMetricString(t *testing.T) {
|
||||||
nopMutator,
|
nopMutator,
|
||||||
nopMutator,
|
nopMutator,
|
||||||
func() storage.Appender { return capp },
|
func() storage.Appender { return capp },
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
@ -901,6 +955,7 @@ func TestScrapeLoopAppendStaleness(t *testing.T) {
|
||||||
nopMutator,
|
nopMutator,
|
||||||
nopMutator,
|
nopMutator,
|
||||||
func() storage.Appender { return app },
|
func() storage.Appender { return app },
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
@ -945,6 +1000,7 @@ func TestScrapeLoopAppendNoStalenessIfTimestamp(t *testing.T) {
|
||||||
nopMutator,
|
nopMutator,
|
||||||
nopMutator,
|
nopMutator,
|
||||||
func() storage.Appender { return app },
|
func() storage.Appender { return app },
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
@ -983,6 +1039,7 @@ func TestScrapeLoopRunReportsTargetDownOnScrapeError(t *testing.T) {
|
||||||
nopMutator,
|
nopMutator,
|
||||||
nopMutator,
|
nopMutator,
|
||||||
app,
|
app,
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
scraper.scrapeFunc = func(ctx context.Context, w io.Writer) error {
|
scraper.scrapeFunc = func(ctx context.Context, w io.Writer) error {
|
||||||
|
@ -1011,6 +1068,7 @@ func TestScrapeLoopRunReportsTargetDownOnInvalidUTF8(t *testing.T) {
|
||||||
nopMutator,
|
nopMutator,
|
||||||
nopMutator,
|
nopMutator,
|
||||||
app,
|
app,
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
scraper.scrapeFunc = func(ctx context.Context, w io.Writer) error {
|
scraper.scrapeFunc = func(ctx context.Context, w io.Writer) error {
|
||||||
|
@ -1056,6 +1114,7 @@ func TestScrapeLoopAppendGracefullyIfAmendOrOutOfOrderOrOutOfBounds(t *testing.T
|
||||||
nopMutator,
|
nopMutator,
|
||||||
nopMutator,
|
nopMutator,
|
||||||
func() storage.Appender { return app },
|
func() storage.Appender { return app },
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
now := time.Unix(1, 0)
|
now := time.Unix(1, 0)
|
||||||
|
@ -1088,6 +1147,7 @@ func TestScrapeLoopOutOfBoundsTimeError(t *testing.T) {
|
||||||
maxTime: timestamp.FromTime(time.Now().Add(10 * time.Minute)),
|
maxTime: timestamp.FromTime(time.Now().Add(10 * time.Minute)),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
now := time.Now().Add(20 * time.Minute)
|
now := time.Now().Add(20 * time.Minute)
|
||||||
|
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/prometheus/prometheus/discovery/targetgroup"
|
"github.com/prometheus/prometheus/discovery/targetgroup"
|
||||||
"github.com/prometheus/prometheus/pkg/labels"
|
"github.com/prometheus/prometheus/pkg/labels"
|
||||||
"github.com/prometheus/prometheus/pkg/relabel"
|
"github.com/prometheus/prometheus/pkg/relabel"
|
||||||
|
"github.com/prometheus/prometheus/pkg/textparse"
|
||||||
"github.com/prometheus/prometheus/pkg/value"
|
"github.com/prometheus/prometheus/pkg/value"
|
||||||
"github.com/prometheus/prometheus/storage"
|
"github.com/prometheus/prometheus/storage"
|
||||||
)
|
)
|
||||||
|
@ -56,6 +57,7 @@ type Target struct {
|
||||||
lastError error
|
lastError error
|
||||||
lastScrape time.Time
|
lastScrape time.Time
|
||||||
health TargetHealth
|
health TargetHealth
|
||||||
|
metadata metricMetadataStore
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTarget creates a reasonably configured target for querying.
|
// NewTarget creates a reasonably configured target for querying.
|
||||||
|
@ -72,6 +74,45 @@ func (t *Target) String() string {
|
||||||
return t.URL().String()
|
return t.URL().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type metricMetadataStore interface {
|
||||||
|
listMetadata() []MetricMetadata
|
||||||
|
getMetadata(metric string) (MetricMetadata, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MetricMetadata is a piece of metadata for a metric.
|
||||||
|
type MetricMetadata struct {
|
||||||
|
Metric string
|
||||||
|
Type textparse.MetricType
|
||||||
|
Help string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Target) MetadataList() []MetricMetadata {
|
||||||
|
t.mtx.RLock()
|
||||||
|
defer t.mtx.RUnlock()
|
||||||
|
|
||||||
|
if t.metadata == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return t.metadata.listMetadata()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata returns type and help metadata for the given metric.
|
||||||
|
func (t *Target) Metadata(metric string) (MetricMetadata, bool) {
|
||||||
|
t.mtx.RLock()
|
||||||
|
defer t.mtx.RUnlock()
|
||||||
|
|
||||||
|
if t.metadata == nil {
|
||||||
|
return MetricMetadata{}, false
|
||||||
|
}
|
||||||
|
return t.metadata.getMetadata(metric)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Target) setMetadataStore(s metricMetadataStore) {
|
||||||
|
t.mtx.Lock()
|
||||||
|
defer t.mtx.Unlock()
|
||||||
|
t.metadata = s
|
||||||
|
}
|
||||||
|
|
||||||
// hash returns an identifying hash for the target.
|
// hash returns an identifying hash for the target.
|
||||||
func (t *Target) hash() uint64 {
|
func (t *Target) hash() uint64 {
|
||||||
h := fnv.New64a()
|
h := fnv.New64a()
|
||||||
|
|
|
@ -35,6 +35,7 @@ import (
|
||||||
|
|
||||||
"github.com/prometheus/prometheus/config"
|
"github.com/prometheus/prometheus/config"
|
||||||
"github.com/prometheus/prometheus/pkg/labels"
|
"github.com/prometheus/prometheus/pkg/labels"
|
||||||
|
"github.com/prometheus/prometheus/pkg/textparse"
|
||||||
"github.com/prometheus/prometheus/pkg/timestamp"
|
"github.com/prometheus/prometheus/pkg/timestamp"
|
||||||
"github.com/prometheus/prometheus/prompb"
|
"github.com/prometheus/prometheus/prompb"
|
||||||
"github.com/prometheus/prometheus/promql"
|
"github.com/prometheus/prometheus/promql"
|
||||||
|
@ -63,6 +64,7 @@ const (
|
||||||
errorBadData errorType = "bad_data"
|
errorBadData errorType = "bad_data"
|
||||||
errorInternal errorType = "internal"
|
errorInternal errorType = "internal"
|
||||||
errorUnavailable errorType = "unavailable"
|
errorUnavailable errorType = "unavailable"
|
||||||
|
errorNotFound errorType = "not_found"
|
||||||
)
|
)
|
||||||
|
|
||||||
var corsHeaders = map[string]string{
|
var corsHeaders = map[string]string{
|
||||||
|
@ -186,6 +188,7 @@ func (api *API) Register(r *route.Router) {
|
||||||
r.Del("/series", wrap(api.dropSeries))
|
r.Del("/series", wrap(api.dropSeries))
|
||||||
|
|
||||||
r.Get("/targets", wrap(api.targets))
|
r.Get("/targets", wrap(api.targets))
|
||||||
|
r.Get("/targets/metadata", wrap(api.targetMetadata))
|
||||||
r.Get("/alertmanagers", wrap(api.alertmanagers))
|
r.Get("/alertmanagers", wrap(api.alertmanagers))
|
||||||
|
|
||||||
r.Get("/status/config", wrap(api.serveConfig))
|
r.Get("/status/config", wrap(api.serveConfig))
|
||||||
|
@ -461,7 +464,6 @@ func (api *API) targets(r *http.Request) (interface{}, *apiError, func()) {
|
||||||
res := &TargetDiscovery{ActiveTargets: make([]*Target, len(tActive)), DroppedTargets: make([]*DroppedTarget, len(tDropped))}
|
res := &TargetDiscovery{ActiveTargets: make([]*Target, len(tActive)), DroppedTargets: make([]*DroppedTarget, len(tDropped))}
|
||||||
|
|
||||||
for i, t := range tActive {
|
for i, t := range tActive {
|
||||||
|
|
||||||
lastErrStr := ""
|
lastErrStr := ""
|
||||||
lastErr := t.LastError()
|
lastErr := t.LastError()
|
||||||
if lastErr != nil {
|
if lastErr != nil {
|
||||||
|
@ -486,6 +488,76 @@ func (api *API) targets(r *http.Request) (interface{}, *apiError, func()) {
|
||||||
return res, nil, nil
|
return res, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api *API) targetMetadata(r *http.Request) (interface{}, *apiError) {
|
||||||
|
limit := -1
|
||||||
|
if s := r.FormValue("limit"); s != "" {
|
||||||
|
var err error
|
||||||
|
if limit, err = strconv.Atoi(s); err != nil {
|
||||||
|
return nil, &apiError{errorBadData, fmt.Errorf("limit must be a number")}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
matchers, err := promql.ParseMetricSelector(r.FormValue("match"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, &apiError{errorBadData, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
var metric string
|
||||||
|
for i, m := range matchers {
|
||||||
|
// Extract metric matcher.
|
||||||
|
if m.Name == labels.MetricName && m.Type == labels.MatchEqual {
|
||||||
|
metric = m.Value
|
||||||
|
matchers = append(matchers[:i], matchers[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []metricMetadata
|
||||||
|
Outer:
|
||||||
|
for _, t := range api.targetRetriever.TargetsActive() {
|
||||||
|
if limit >= 0 && len(res) >= limit {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for _, m := range matchers {
|
||||||
|
// Filter targets that don't satisfy the label matchers.
|
||||||
|
if !m.Matches(t.Labels().Get(m.Name)) {
|
||||||
|
continue Outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If no metric is specified, get the full list for the target.
|
||||||
|
if metric == "" {
|
||||||
|
for _, md := range t.MetadataList() {
|
||||||
|
res = append(res, metricMetadata{
|
||||||
|
Target: t.Labels(),
|
||||||
|
Metric: md.Metric,
|
||||||
|
Type: md.Type,
|
||||||
|
Help: md.Help,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Get metadata for the specified metric.
|
||||||
|
if md, ok := t.Metadata(metric); ok {
|
||||||
|
res = append(res, metricMetadata{
|
||||||
|
Target: t.Labels(),
|
||||||
|
Type: md.Type,
|
||||||
|
Help: md.Help,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(res) == 0 {
|
||||||
|
return nil, &apiError{errorNotFound, errors.New("specified metadata not found")}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type metricMetadata struct {
|
||||||
|
Target labels.Labels `json:"target"`
|
||||||
|
Metric string `json:"metric,omitempty"`
|
||||||
|
Type textparse.MetricType `json:"type"`
|
||||||
|
Help string `json:"help"`
|
||||||
|
}
|
||||||
|
|
||||||
// AlertmanagerDiscovery has all the active Alertmanagers.
|
// AlertmanagerDiscovery has all the active Alertmanagers.
|
||||||
type AlertmanagerDiscovery struct {
|
type AlertmanagerDiscovery struct {
|
||||||
ActiveAlertmanagers []*AlertmanagerTarget `json:"activeAlertmanagers"`
|
ActiveAlertmanagers []*AlertmanagerTarget `json:"activeAlertmanagers"`
|
||||||
|
@ -783,6 +855,8 @@ func respondError(w http.ResponseWriter, apiErr *apiError, data interface{}) {
|
||||||
code = http.StatusServiceUnavailable
|
code = http.StatusServiceUnavailable
|
||||||
case errorInternal:
|
case errorInternal:
|
||||||
code = http.StatusInternalServerError
|
code = http.StatusInternalServerError
|
||||||
|
case errorNotFound:
|
||||||
|
code = http.StatusNotFound
|
||||||
default:
|
default:
|
||||||
code = http.StatusInternalServerError
|
code = http.StatusInternalServerError
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue