Merge pull request #786 from prometheus/fabxc/federate
web: add basic federation support
This commit is contained in:
commit
e18bc94980
|
@ -21,9 +21,9 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// ExporterLabelPrefix is the label name prefix to prepend if a
|
||||
// ExportedLabelPrefix is the label name prefix to prepend if a
|
||||
// synthetic label is already present in the exported metrics.
|
||||
ExporterLabelPrefix LabelName = "exporter_"
|
||||
ExportedLabelPrefix LabelName = "exported_"
|
||||
|
||||
// MetricNameLabel is the label name indicating the metric name of a
|
||||
// timeseries.
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -62,6 +63,7 @@ var (
|
|||
// configured globals.
|
||||
MetricsPath: "/metrics",
|
||||
Scheme: "http",
|
||||
HonorLabels: false,
|
||||
}
|
||||
|
||||
// The default Relabel configuration.
|
||||
|
@ -190,6 +192,10 @@ func (c *GlobalConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
type ScrapeConfig struct {
|
||||
// The job name to which the job label is set by default.
|
||||
JobName string `yaml:"job_name"`
|
||||
// Indicator whether the scraped metrics should remain unmodified.
|
||||
HonorLabels bool `yaml:"honor_labels,omitempty"`
|
||||
// A set of query parameters with which the target is scraped.
|
||||
Params url.Values `yaml:"params,omitempty"`
|
||||
// How frequently to scrape the targets of this scrape config.
|
||||
ScrapeInterval Duration `yaml:"scrape_interval,omitempty"`
|
||||
// The timeout for scraping targets of this config.
|
||||
|
|
|
@ -36,6 +36,7 @@ var expectedConf = &Config{
|
|||
{
|
||||
JobName: "prometheus",
|
||||
|
||||
HonorLabels: true,
|
||||
ScrapeInterval: Duration(15 * time.Second),
|
||||
ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ rule_files:
|
|||
scrape_configs:
|
||||
- job_name: prometheus
|
||||
|
||||
honor_labels: true
|
||||
# scrape_interval is defined by the configured global (15s).
|
||||
# scrape_timeout is defined by the global default (10s).
|
||||
|
||||
|
|
|
@ -38,6 +38,11 @@ type collectResultAppender struct {
|
|||
}
|
||||
|
||||
func (a *collectResultAppender) Append(s *clientmodel.Sample) {
|
||||
for ln, lv := range s.Metric {
|
||||
if len(lv) == 0 {
|
||||
delete(s.Metric, ln)
|
||||
}
|
||||
}
|
||||
a.result = append(a.result, s)
|
||||
}
|
||||
|
||||
|
|
|
@ -166,6 +166,9 @@ type Target struct {
|
|||
deadline time.Duration
|
||||
// The time between two scrapes.
|
||||
scrapeInterval time.Duration
|
||||
// Whether the target's labels have precedence over the base labels
|
||||
// assigned by the scraping instance.
|
||||
honorLabels bool
|
||||
}
|
||||
|
||||
// NewTarget creates a reasonably configured target for querying.
|
||||
|
@ -198,11 +201,13 @@ func (t *Target) Update(cfg *config.ScrapeConfig, baseLabels, metaLabels clientm
|
|||
if cfg.BasicAuth != nil {
|
||||
t.url.User = url.UserPassword(cfg.BasicAuth.Username, cfg.BasicAuth.Password)
|
||||
}
|
||||
t.url.RawQuery = cfg.Params.Encode()
|
||||
|
||||
t.scrapeInterval = time.Duration(cfg.ScrapeInterval)
|
||||
t.deadline = time.Duration(cfg.ScrapeTimeout)
|
||||
t.httpClient = httputil.NewDeadlineClient(time.Duration(cfg.ScrapeTimeout))
|
||||
|
||||
t.honorLabels = cfg.HonorLabels
|
||||
t.metaLabels = metaLabels
|
||||
t.baseLabels = clientmodel.LabelSet{}
|
||||
// All remaining internal labels will not be part of the label set.
|
||||
|
@ -331,7 +336,7 @@ func (t *Target) scrape(sampleAppender storage.SampleAppender) (err error) {
|
|||
recordScrapeHealth(sampleAppender, clientmodel.TimestampFromTime(start), baseLabels, t.status.Health(), time.Since(start))
|
||||
}()
|
||||
|
||||
req, err := http.NewRequest("GET", t.URL(), nil)
|
||||
req, err := http.NewRequest("GET", t.URL().String(), nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -363,12 +368,29 @@ func (t *Target) scrape(sampleAppender storage.SampleAppender) (err error) {
|
|||
|
||||
for samples := range t.ingestedSamples {
|
||||
for _, s := range samples {
|
||||
s.Metric.MergeFromLabelSet(baseLabels, clientmodel.ExporterLabelPrefix)
|
||||
if t.honorLabels {
|
||||
// Merge the metric with the baseLabels for labels not already set in the
|
||||
// metric. This also considers labels explicitly set to the empty string.
|
||||
for ln, lv := range baseLabels {
|
||||
if _, ok := s.Metric[ln]; !ok {
|
||||
s.Metric[ln] = lv
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Merge the ingested metric with the base label set. On a collision the
|
||||
// value of the label is stored in a label prefixed with the exported prefix.
|
||||
for ln, lv := range baseLabels {
|
||||
if v, ok := s.Metric[ln]; ok && v != "" {
|
||||
s.Metric[clientmodel.ExportedLabelPrefix+ln] = v
|
||||
}
|
||||
s.Metric[ln] = lv
|
||||
}
|
||||
}
|
||||
// Avoid the copy in Relabel if there are no configs.
|
||||
if len(t.metricRelabelConfigs) > 0 {
|
||||
labels, err := Relabel(clientmodel.LabelSet(s.Metric), t.metricRelabelConfigs...)
|
||||
if err != nil {
|
||||
log.Errorf("error while relabeling metric %s of instance %s: ", s.Metric, t.url, err)
|
||||
log.Errorf("Error while relabeling metric %s of instance %s: %s", s.Metric, t.url, err)
|
||||
continue
|
||||
}
|
||||
// Check if the timeseries was dropped.
|
||||
|
@ -383,11 +405,13 @@ func (t *Target) scrape(sampleAppender storage.SampleAppender) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
// URL implements Target.
|
||||
func (t *Target) URL() string {
|
||||
// URL returns a copy of the target's URL.
|
||||
func (t *Target) URL() *url.URL {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
return t.url.String()
|
||||
u := &url.URL{}
|
||||
*u = *t.url
|
||||
return u
|
||||
}
|
||||
|
||||
// InstanceIdentifier returns the identifier for the target.
|
||||
|
|
|
@ -44,6 +44,91 @@ func TestBaseLabels(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestOverwriteLabels(t *testing.T) {
|
||||
type test struct {
|
||||
metric string
|
||||
resultNormal clientmodel.Metric
|
||||
resultHonor clientmodel.Metric
|
||||
}
|
||||
var tests []test
|
||||
|
||||
server := httptest.NewServer(
|
||||
http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", `text/plain; version=0.0.4`)
|
||||
for _, test := range tests {
|
||||
w.Write([]byte(test.metric))
|
||||
w.Write([]byte(" 1\n"))
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
defer server.Close()
|
||||
addr := clientmodel.LabelValue(strings.Split(server.URL, "://")[1])
|
||||
|
||||
tests = []test{
|
||||
{
|
||||
metric: `foo{}`,
|
||||
resultNormal: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "foo",
|
||||
clientmodel.InstanceLabel: addr,
|
||||
},
|
||||
resultHonor: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "foo",
|
||||
clientmodel.InstanceLabel: addr,
|
||||
},
|
||||
},
|
||||
{
|
||||
metric: `foo{instance=""}`,
|
||||
resultNormal: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "foo",
|
||||
clientmodel.InstanceLabel: addr,
|
||||
},
|
||||
resultHonor: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "foo",
|
||||
},
|
||||
},
|
||||
{
|
||||
metric: `foo{instance="other_instance"}`,
|
||||
resultNormal: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "foo",
|
||||
clientmodel.InstanceLabel: addr,
|
||||
clientmodel.ExportedLabelPrefix + clientmodel.InstanceLabel: "other_instance",
|
||||
},
|
||||
resultHonor: clientmodel.Metric{
|
||||
clientmodel.MetricNameLabel: "foo",
|
||||
clientmodel.InstanceLabel: "other_instance",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
target := newTestTarget(server.URL, 10*time.Millisecond, nil)
|
||||
|
||||
target.honorLabels = false
|
||||
app := &collectResultAppender{}
|
||||
if err := target.scrape(app); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
if !reflect.DeepEqual(app.result[i].Metric, test.resultNormal) {
|
||||
t.Errorf("Error comparing %q:\nExpected:\n%s\nGot:\n%s\n", test.metric, test.resultNormal, app.result[i].Metric)
|
||||
}
|
||||
}
|
||||
|
||||
target.honorLabels = true
|
||||
app = &collectResultAppender{}
|
||||
if err := target.scrape(app); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
if !reflect.DeepEqual(app.result[i].Metric, test.resultHonor) {
|
||||
t.Errorf("Error comparing %q:\nExpected:\n%s\nGot:\n%s\n", test.metric, test.resultHonor, app.result[i].Metric)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
func TestTargetScrapeUpdatesState(t *testing.T) {
|
||||
testTarget := newTestTarget("bad schema", 0, nil)
|
||||
|
||||
|
|
|
@ -142,6 +142,20 @@ func (cd *chunkDesc) lastTime() clientmodel.Timestamp {
|
|||
return cd.c.newIterator().lastTimestamp()
|
||||
}
|
||||
|
||||
func (cd *chunkDesc) lastSamplePair() *metric.SamplePair {
|
||||
cd.Lock()
|
||||
defer cd.Unlock()
|
||||
|
||||
if cd.c == nil {
|
||||
return nil
|
||||
}
|
||||
it := cd.c.newIterator()
|
||||
return &metric.SamplePair{
|
||||
Timestamp: it.lastTimestamp(),
|
||||
Value: it.lastSampleValue(),
|
||||
}
|
||||
}
|
||||
|
||||
func (cd *chunkDesc) isEvicted() bool {
|
||||
cd.Lock()
|
||||
defer cd.Unlock()
|
||||
|
|
|
@ -40,6 +40,9 @@ type Storage interface {
|
|||
// label matchers. At least one label matcher must be specified that does not
|
||||
// match the empty string.
|
||||
MetricsForLabelMatchers(matchers ...*metric.LabelMatcher) map[clientmodel.Fingerprint]clientmodel.COWMetric
|
||||
// LastSamplePairForFingerprint returns the last sample pair for the provided fingerprint.
|
||||
// If the respective time series is evicted, nil is returned.
|
||||
LastSamplePairForFingerprint(clientmodel.Fingerprint) *metric.SamplePair
|
||||
// Get all of the label values that are associated with a given label name.
|
||||
LabelValuesForLabelName(clientmodel.LabelName) clientmodel.LabelValues
|
||||
// Get the metric associated with the provided fingerprint.
|
||||
|
|
|
@ -301,7 +301,7 @@ func (s *memorySeriesStorage) WaitForIndexing() {
|
|||
s.persistence.waitForIndexing()
|
||||
}
|
||||
|
||||
// NewIterator implements storage.
|
||||
// NewIterator implements Storage.
|
||||
func (s *memorySeriesStorage) NewIterator(fp clientmodel.Fingerprint) SeriesIterator {
|
||||
s.fpLocker.Lock(fp)
|
||||
defer s.fpLocker.Unlock(fp)
|
||||
|
@ -321,6 +321,18 @@ func (s *memorySeriesStorage) NewIterator(fp clientmodel.Fingerprint) SeriesIter
|
|||
}
|
||||
}
|
||||
|
||||
// LastSampleForFingerprint implements Storage.
|
||||
func (s *memorySeriesStorage) LastSamplePairForFingerprint(fp clientmodel.Fingerprint) *metric.SamplePair {
|
||||
s.fpLocker.Lock(fp)
|
||||
defer s.fpLocker.Unlock(fp)
|
||||
|
||||
series, ok := s.fpToSeries.get(fp)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return series.head().lastSamplePair()
|
||||
}
|
||||
|
||||
// boundedIterator wraps a SeriesIterator and does not allow fetching
|
||||
// data from earlier than the configured start time.
|
||||
type boundedIterator struct {
|
||||
|
@ -510,6 +522,11 @@ func (s *memorySeriesStorage) DropMetricsForFingerprints(fps ...clientmodel.Fing
|
|||
|
||||
// Append implements Storage.
|
||||
func (s *memorySeriesStorage) Append(sample *clientmodel.Sample) {
|
||||
for ln, lv := range sample.Metric {
|
||||
if len(lv) == 0 {
|
||||
delete(sample.Metric, ln)
|
||||
}
|
||||
}
|
||||
if s.getNumChunksToPersist() >= s.maxChunksToPersist {
|
||||
log.Warnf(
|
||||
"%d chunks waiting for persistence, sample ingestion suspended.",
|
||||
|
|
|
@ -165,7 +165,7 @@ func templatesGraphHtml() (*asset, error) {
|
|||
return a, nil
|
||||
}
|
||||
|
||||
var _templatesStatusHtml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xcc\x56\xcb\x6e\xdb\x3a\x10\xdd\xfb\x2b\x78\x85\x2c\xaf\x6c\xe0\x02\x77\x13\xd8\x5e\x38\x48\x91\x00\x4e\x11\xe4\xb1\xe9\x26\xa0\xa4\xb1\xc4\x96\x21\x05\x92\x4a\x13\x28\xfa\xf7\xce\x50\x92\x2d\xeb\xd1\x06\x7d\xa0\xd9\x08\x1c\x72\x34\x8f\x73\xce\x50\x2a\xcb\x04\x76\x42\x01\x0b\x32\xe0\x49\x50\x55\xcb\x7f\xc2\x90\x29\xf1\xcc\xc2\x70\x5d\x96\xa0\x92\xaa\x9a\xcd\x0e\x5e\xb1\x56\x0e\x94\x43\xc7\x19\x63\xcb\x44\x3c\xb1\x58\x72\x6b\x57\xfe\x80\xa3\x8b\x09\x77\xb2\x10\x49\xb0\xc6\x73\xf4\xc8\xfe\x5b\xdf\x14\xca\x89\x47\x60\x97\x6a\xa7\xcd\x23\x77\x42\xab\xe5\x02\xf7\x6b\x07\xc7\x23\x09\x6d\x90\xda\xf0\xcf\x10\x03\x26\xa0\x2c\x24\x8d\x1d\x69\x93\x80\xd9\x9b\xd6\x19\x91\xef\xad\x4c\x3f\x81\x69\x72\x52\xd0\x48\x27\x2f\xad\x45\xb6\x39\x18\x64\x66\xeb\xfb\x9c\x6a\x5a\x2e\x70\x79\x74\x92\x60\xd3\xf3\x5b\xc7\x5d\x61\xe7\x1b\x61\x5c\x86\x88\x2c\x70\xf7\x10\x6b\x71\x08\x86\xeb\x43\x22\x34\xa8\x94\xf5\x6c\xdf\xf8\xa6\x10\x32\xf9\x8b\x6d\x97\xa5\xe1\x2a\x05\x76\xf2\x05\x5e\xfe\x65\x27\x4f\x5c\x16\xc0\x4e\x57\x6c\x4e\x25\x79\x02\xa7\xe0\x61\x36\xd6\x39\xac\x02\xa3\xbf\x06\x08\x08\x05\xf0\x38\x8c\x80\x55\x87\xfd\x1e\x4a\x54\x48\xad\xa3\x37\xa1\x76\xa6\xd5\x4e\xa4\x85\xe9\x23\x96\x1b\xe8\x70\x53\x7b\x51\x5a\xda\x9f\x75\xc4\x26\xc1\x0e\xde\xaa\x71\x88\xb9\x94\xac\x0d\xe0\x1d\xab\x0a\x23\x5e\xdc\x5d\x6d\x6f\x95\xc8\x73\x70\x2c\xe7\x2e\xbb\x36\xa8\xf5\x67\x0c\x1d\x99\x45\x3b\x02\xfd\x34\x77\xdc\xa4\xe0\x3a\x89\xfe\x10\xa5\x1d\x12\x3f\xeb\x08\x49\xcc\xb5\x96\xc4\xe1\x51\x2f\x75\x35\xd7\x78\x64\x3b\xb4\x7a\x26\x71\xa8\xbb\x9c\xd5\x64\x13\xc3\x31\x3a\xe7\x5c\xad\x82\xff\x83\xb6\x66\xcc\xf0\x40\x2f\x50\x7e\x24\x16\xcd\x86\xf4\x63\x36\x47\x24\xd3\x24\x5b\x9f\xab\x24\xd7\x42\xb9\xbe\x54\xda\x73\xaa\x77\x30\x74\xed\xe1\x86\x5b\x60\x5b\x1e\x81\xb4\x53\x2e\x5b\x6e\x1d\xbb\x8d\x0d\xcf\x27\xa3\x9c\x1b\xa3\xcd\xf0\xb0\xdf\x02\x79\xf4\xb0\xe9\x8f\x4f\x07\x7b\x42\xfd\x08\xd9\x09\x04\x92\xfe\x16\x6e\x72\x96\xa1\xa0\x56\x01\x2a\xed\xfe\x66\xcb\x5e\x59\x2a\x75\xc4\x25\xae\xab\x8a\x70\x9e\xfb\xd5\x72\xc1\x07\xe1\x16\xc3\x78\xe3\x29\x88\xc8\x96\x44\x2e\xc1\x38\xe6\x9f\x61\x59\xee\x25\x72\x01\x5c\x22\xeb\xaf\x2c\xf3\x8b\x3b\x7d\x46\xee\xac\xaa\x50\x76\x24\x9e\x07\xeb\xfd\x1e\xfc\x8b\xc1\x30\x07\x81\x71\x1c\xab\x87\x47\x5d\x31\x55\xf2\x7b\xfa\x88\x0b\x63\xb5\x09\xbd\x9c\x50\x90\x2c\xe1\x8e\x87\x4e\xa7\xa9\xc4\x7b\xc9\x21\x1f\x4e\xe4\x01\x73\xc2\x91\xdd\x1c\x6b\x23\x52\xa1\xb8\x0c\x9b\xed\x0d\xe0\xd5\x0b\xcc\x80\x24\x55\x09\x95\x9e\x52\x17\x57\xe0\x78\x2d\x33\x22\x60\xb4\xd3\x93\x08\xb5\x58\xfb\xd0\xb0\xf9\x09\x6d\xcc\xf9\xe6\x70\x44\x13\x13\xb0\x40\x28\x04\x4f\xc5\x10\x8c\x40\x42\xd1\xc4\x8e\x75\x02\x8e\x3a\x75\xb4\xe6\x6b\xed\x5e\xd7\x3f\x7e\xb7\x07\x9d\x8f\xc0\xfc\x33\xcc\x8d\x78\xe4\xe6\xc5\x0f\xb4\xdf\xa9\x2a\x92\x62\x7b\x6b\x07\xe3\x94\xb5\x25\x75\xaf\xed\xde\x89\xb4\x30\x51\xcd\x64\x2d\xf8\x0f\xc1\x0b\x89\xea\x52\x5a\xc1\x74\xe2\xa9\xb4\xbf\xa8\x2e\xcf\x43\x2b\x61\xba\x48\xea\x7b\x64\x7e\x69\x3f\x81\xc1\xcf\xe1\x47\xc0\x8b\xb7\x6d\xac\x2c\xad\x40\x46\x47\xfc\x71\x62\x78\xaa\xc7\x6b\xfc\xf9\x5a\xfc\xad\x35\xd6\xf3\xd4\x68\x27\x24\x16\x33\x3e\xbc\x87\x49\xed\x84\x9e\xc2\xfb\xad\x9d\xf4\x2f\xd0\xe1\x7b\x47\xdf\xf5\xa1\xcb\xf0\x4b\x8f\x45\x1a\x57\xe4\xec\x83\xe4\xa9\x7d\x2f\xff\x46\x0d\x72\xbe\xa6\xf7\xf6\x8f\x44\x4b\xfc\xe5\x5e\xcf\x5a\xe7\x6f\x01\x00\x00\xff\xff\x50\x97\xbd\xdc\xbe\x0b\x00\x00")
|
||||
var _templatesStatusHtml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xcc\x57\xcd\x8e\xdb\x36\x10\xbe\xfb\x29\x58\x61\x8f\x95\x05\x14\xe8\x65\x61\xeb\xe0\x20\xc5\x06\xd8\x14\xdb\xec\xe6\xd2\xcb\x82\x92\xc6\x12\x5b\x9a\x14\x48\x6a\x1b\x43\xd1\xbb\x77\x86\x92\x6c\xfd\x76\xdb\x06\x41\x72\x31\x38\xe4\x70\x7e\xbe\xf9\x66\x44\xd7\x75\x06\x47\xa1\x80\x05\x05\xf0\x2c\x68\x9a\xdd\x0f\x61\xc8\x94\xf8\xc4\xc2\x30\xae\x6b\x50\x59\xd3\x6c\x36\x57\xad\x54\x2b\x07\xca\xa1\xe2\x86\xb1\x5d\x26\x5e\x58\x2a\xb9\xb5\x7b\x7f\xc0\x51\xc5\x84\x47\x59\x89\x2c\x88\xf1\x1c\x35\x8a\x9f\xe2\x0f\x95\x72\xe2\x04\xec\x9d\x3a\x6a\x73\xe2\x4e\x68\xb5\x8b\x70\xbf\x55\x70\x3c\x91\xd0\x1b\x69\x05\xff\x1b\xa2\xc1\x0c\x94\x85\xac\x93\x13\x6d\x32\x30\x17\xd1\x3a\x23\xca\x8b\x54\xe8\x17\x30\x9d\x4f\x32\x9a\xe8\xec\xdc\x4b\x24\x9b\xab\x40\x62\x11\x7f\x2c\x29\xa6\x5d\x84\xcb\xd1\x49\x86\x49\x6f\x1f\x1d\x77\x95\xdd\x1e\x84\x71\x05\x22\x12\xe1\xee\xd5\x56\x74\x35\x86\xeb\xab\x23\x14\x28\x94\x78\x73\x49\xfc\x50\x09\x99\x7d\xc3\xb4\xeb\xda\x70\x95\x03\xbb\xf9\x13\xce\x3f\xb2\x9b\x17\x2e\x2b\x60\xb7\x7b\xb6\xa5\x90\x7c\x01\xd7\xe0\x61\x36\xd5\x25\xec\x03\xa3\xff\x0a\x10\x10\x32\xe0\x71\x58\x00\xab\x35\xfb\x4f\x28\x51\x20\x2d\x8f\xfe\x15\x6a\x6f\xb4\x3a\x8a\xbc\x32\x53\xc4\x4a\x03\x83\xda\xb4\x5a\xe4\x96\xf6\x37\x03\xb2\x49\xb0\xb3\x5b\x2d\x0e\x29\x97\x92\xf5\x06\xbc\x62\xd3\xa0\xc5\xbb\xa7\xf7\xf7\x8f\x4a\x94\x25\x38\x56\x72\x57\x3c\x18\xe4\xfa\x27\x34\x9d\x98\xa8\x6f\x81\xa9\x9b\x27\x6e\x72\x70\x03\x47\x5f\xa9\xa4\x83\x22\xfe\xa1\x13\x2c\x62\xa9\xb5\xa4\x1a\x8e\x72\x69\xa3\x79\xc0\x23\x3b\x28\xab\xaf\x24\x36\xf5\xb0\x66\x6d\xb1\xa9\xc2\x29\x2a\x97\x5c\xed\x83\x9f\x83\x3e\x66\xf4\xf0\x4c\x17\xc8\x3f\x16\x16\xc5\xae\xe8\xe3\x6a\x2e\x50\xa6\x73\x16\xbf\x55\x59\xa9\x85\x72\x53\xaa\xf4\xe7\x14\xef\xac\xe9\xfa\xc3\x03\xb7\xc0\xee\x79\x02\xd2\xae\xa9\xdc\x73\xeb\xd8\x63\x6a\x78\xb9\x6a\xe5\xad\x31\xda\xcc\x0f\xa7\x29\x90\xc6\x04\x9b\x69\xfb\x0c\xb0\x27\xd4\x47\xc8\xae\x20\x90\xcd\xb6\x38\x2b\x90\x4e\xfb\x00\x79\xf6\xf1\xc3\x3d\xfb\xcc\x72\xa9\x13\x2e\x71\xdd\x34\x84\x32\xed\x6e\x1f\xd3\x02\x4e\xd8\x43\xb7\x51\xd4\xed\xdc\x69\xeb\x3c\x39\x49\x78\xe0\xed\x18\xe2\x31\x52\x72\xea\x61\x10\xa5\x24\xec\xfa\x46\xb7\xbe\xd3\xe9\xfa\x6f\x15\x98\x33\x9b\x84\x3f\xba\x28\x86\xd3\xa1\xbb\xbe\xa0\xbf\x23\xc2\xf4\x64\xf1\xce\x98\xff\x0d\x4b\x23\x4e\xdc\x9c\x3d\x6b\xfc\x4e\xd3\x50\xc6\xfd\x68\x08\x76\x11\xdd\x5c\x8a\x7c\x38\x17\x5e\xdb\x1f\x4f\x98\x55\xc8\x27\x71\x72\x09\xc6\x31\xff\x1b\xd6\xf5\xa5\x65\xee\x80\x4b\xec\x82\xcf\xac\xf0\x8b\x27\xfd\x86\xd4\x11\x25\x6c\x43\x6a\xa6\x67\xeb\xf5\x9e\xfd\xc5\x60\xee\x83\x82\x1c\xdb\x5a\x02\x6c\x31\xef\xff\x97\x47\x5a\x19\xab\x4d\xe8\xdb\x0b\x1b\x94\x65\xdc\xf1\xd0\xe9\x3c\x97\x38\xa7\x1d\xf2\xd3\x89\x32\x60\x4e\x38\x92\xbb\x63\x6d\x44\x2e\x14\x97\x61\xb7\x7d\x00\xfc\x14\x01\x33\xe0\x8b\x24\x54\x7e\x4b\x59\xbc\x07\xc7\xdb\xb6\x23\x4a\x2e\x66\x7a\x93\x60\x6f\xb6\x3a\x44\x11\x3f\xb1\x3a\x71\x7b\xb8\x1e\xd1\x04\x09\x58\x20\x14\x82\xa7\x52\x08\x16\x20\x21\x6b\xe2\xc8\x06\x06\x17\x95\xd6\x58\xed\x09\xfa\xea\xdd\xaf\x40\xd5\x36\xa4\x65\xba\xfa\x13\x69\x61\x25\x9a\xd5\x58\xf0\x4d\xc5\x2b\x89\xec\x52\x5a\xc1\xba\xe3\x35\xb7\x5f\xc8\x2e\x5f\x87\x9e\xc2\x34\x58\xdb\xb9\xba\x7d\x67\x7f\x07\x83\xcf\x83\x5f\x01\x3f\x44\x7d\x62\x75\x6d\x05\x56\x74\x41\x1f\x3b\x86\xe7\xfa\x0b\x3b\x76\x16\x8b\x9f\xe2\xaf\x8d\xa0\x61\x6b\x67\x44\x16\xb3\xdc\xbc\xd7\x4e\x1d\x98\xfe\x6f\x33\x69\x9e\xc9\xf4\x83\x32\xbf\x37\x7a\xe7\xcc\x55\xe6\x2f\x1f\x0c\xd2\xb8\xaa\x64\xbf\x48\x9e\xdb\xef\xe5\xad\xd8\x21\xe7\x63\xfa\xde\xde\x8c\xb4\xc4\xbf\x20\xf1\xa6\x57\xfe\x3b\x00\x00\xff\xff\x9d\x54\xec\xc3\xce\x0c\x00\x00")
|
||||
|
||||
func templatesStatusHtmlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
|
@ -180,7 +180,7 @@ func templatesStatusHtml() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "templates/status.html", size: 3006, mode: os.FileMode(420), modTime: time.Unix(1434457438, 0)}
|
||||
info := bindataFileInfo{name: "templates/status.html", size: 3278, mode: os.FileMode(420), modTime: time.Unix(1435006390, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -220,7 +220,7 @@ func staticCssGraphCss() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "static/css/graph.css", size: 2668, mode: os.FileMode(420), modTime: time.Unix(1434193569, 0)}
|
||||
info := bindataFileInfo{name: "static/css/graph.css", size: 2668, mode: os.FileMode(420), modTime: time.Unix(1435004178, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -320,7 +320,7 @@ func staticJsGraphJs() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "static/js/graph.js", size: 19251, mode: os.FileMode(420), modTime: time.Unix(1434193569, 0)}
|
||||
info := bindataFileInfo{name: "static/js/graph.js", size: 19251, mode: os.FileMode(420), modTime: time.Unix(1435004178, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -340,7 +340,7 @@ func staticJsGraph_templateHandlebar() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "static/js/graph_template.handlebar", size: 6365, mode: os.FileMode(420), modTime: time.Unix(1434193569, 0)}
|
||||
info := bindataFileInfo{name: "static/js/graph_template.handlebar", size: 6365, mode: os.FileMode(420), modTime: time.Unix(1435004178, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
|
|
@ -47,7 +47,12 @@
|
|||
{{range $pool}}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{.URL | globalURL}}">{{.URL}}</a>
|
||||
<a href="{{.URL | globalURL}}">{{.URL.Scheme}}://{{.URL.Host}}{{.URL.Path}}</a><br>
|
||||
{{range $label, $values := .URL.Query }}
|
||||
{{range $i, $value := $values}}
|
||||
<span class="label label-primary">{{$label}}="{{$value}}"</span>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</td>
|
||||
<td>
|
||||
<span class="alert alert-{{ .Status.Health | healthToClass }} target_status_alert">
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"bitbucket.org/ww/goautoneg"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/text"
|
||||
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
"github.com/prometheus/prometheus/storage/local"
|
||||
|
||||
clientmodel "github.com/prometheus/client_golang/model"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
)
|
||||
|
||||
type Federation struct {
|
||||
Storage local.Storage
|
||||
}
|
||||
|
||||
func (fed *Federation) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
req.ParseForm()
|
||||
|
||||
metrics := map[clientmodel.Fingerprint]clientmodel.COWMetric{}
|
||||
|
||||
for _, s := range req.Form["match[]"] {
|
||||
matchers, err := promql.ParseMetricSelector(s)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
for fp, met := range fed.Storage.MetricsForLabelMatchers(matchers...) {
|
||||
metrics[fp] = met
|
||||
}
|
||||
}
|
||||
|
||||
enc, contentType := chooseEncoder(req)
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
|
||||
protMetric := &dto.Metric{
|
||||
Label: []*dto.LabelPair{},
|
||||
Untyped: &dto.Untyped{},
|
||||
}
|
||||
protMetricFam := &dto.MetricFamily{
|
||||
Metric: []*dto.Metric{protMetric},
|
||||
Type: dto.MetricType_UNTYPED.Enum(),
|
||||
}
|
||||
|
||||
for fp, met := range metrics {
|
||||
sp := fed.Storage.LastSamplePairForFingerprint(fp)
|
||||
if sp == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Reset label slice.
|
||||
protMetric.Label = protMetric.Label[:0]
|
||||
|
||||
for ln, lv := range met.Metric {
|
||||
if ln == clientmodel.MetricNameLabel {
|
||||
protMetricFam.Name = proto.String(string(lv))
|
||||
continue
|
||||
}
|
||||
protMetric.Label = append(protMetric.Label, &dto.LabelPair{
|
||||
Name: proto.String(string(ln)),
|
||||
Value: proto.String(string(lv)),
|
||||
})
|
||||
}
|
||||
protMetric.TimestampMs = (*int64)(&sp.Timestamp)
|
||||
protMetric.Untyped.Value = (*float64)(&sp.Value)
|
||||
|
||||
if _, err := enc(w, protMetricFam); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type encoder func(w io.Writer, p *dto.MetricFamily) (int, error)
|
||||
|
||||
func chooseEncoder(req *http.Request) (encoder, string) {
|
||||
accepts := goautoneg.ParseAccept(req.Header.Get("Accept"))
|
||||
for _, accept := range accepts {
|
||||
switch {
|
||||
case accept.Type == "application" &&
|
||||
accept.SubType == "vnd.google.protobuf" &&
|
||||
accept.Params["proto"] == "io.prometheus.client.MetricFamily":
|
||||
switch accept.Params["encoding"] {
|
||||
case "delimited":
|
||||
return text.WriteProtoDelimited, prometheus.DelimitedTelemetryContentType
|
||||
case "text":
|
||||
return text.WriteProtoText, prometheus.ProtoTextTelemetryContentType
|
||||
case "compact-text":
|
||||
return text.WriteProtoCompactText, prometheus.ProtoCompactTextTelemetryContentType
|
||||
default:
|
||||
continue
|
||||
}
|
||||
case accept.Type == "text" &&
|
||||
accept.SubType == "plain" &&
|
||||
(accept.Params["version"] == "0.0.4" || accept.Params["version"] == ""):
|
||||
return text.MetricFamilyToText, prometheus.TextTelemetryContentType
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return text.MetricFamilyToText, prometheus.TextTelemetryContentType
|
||||
}
|
19
web/web.go
19
web/web.go
|
@ -54,8 +54,9 @@ type Handler struct {
|
|||
ruleManager *rules.Manager
|
||||
queryEngine *promql.Engine
|
||||
|
||||
apiV1 *v1.API
|
||||
apiLegacy *legacy.API
|
||||
apiV1 *v1.API
|
||||
apiLegacy *legacy.API
|
||||
federation *Federation
|
||||
|
||||
router *route.Router
|
||||
quitCh chan struct{}
|
||||
|
@ -125,6 +126,9 @@ func New(st local.Storage, qe *promql.Engine, rm *rules.Manager, status *Prometh
|
|||
Storage: st,
|
||||
Now: clientmodel.Now,
|
||||
},
|
||||
federation: &Federation{
|
||||
Storage: st,
|
||||
},
|
||||
}
|
||||
|
||||
if o.PathPrefix != "" {
|
||||
|
@ -145,6 +149,7 @@ func New(st local.Storage, qe *promql.Engine, rm *rules.Manager, status *Prometh
|
|||
|
||||
router.Get("/heap", instrf("heap", dumpHeap))
|
||||
|
||||
router.Get("/federate", instrh("federate", h.federation))
|
||||
router.Get(o.MetricsPath, prometheus.Handler().ServeHTTP)
|
||||
|
||||
h.apiLegacy.Register(router.WithPrefix("/api"))
|
||||
|
@ -329,11 +334,13 @@ func (h *Handler) getTemplate(name string) (*template_std.Template, error) {
|
|||
}
|
||||
return lset
|
||||
},
|
||||
"globalURL": func(url string) string {
|
||||
for _, localhostRepresentation := range localhostRepresentations {
|
||||
url = strings.Replace(url, "//"+localhostRepresentation, "//"+h.options.Hostname, 1)
|
||||
"globalURL": func(u *url.URL) *url.URL {
|
||||
for _, lhr := range localhostRepresentations {
|
||||
if strings.HasPrefix(u.Host, lhr+":") {
|
||||
u.Host = strings.Replace(u.Host, lhr+":", h.options.Hostname+":", 1)
|
||||
}
|
||||
}
|
||||
return url
|
||||
return u
|
||||
},
|
||||
"healthToClass": func(th retrieval.TargetHealth) string {
|
||||
switch th {
|
||||
|
|
Loading…
Reference in New Issue