Merge pull request #786 from prometheus/fabxc/federate

web: add basic federation support
This commit is contained in:
Fabian Reinartz 2015-06-23 14:17:28 +02:00
commit e18bc94980
14 changed files with 297 additions and 21 deletions

View File

@ -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.

View File

@ -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.

View File

@ -36,6 +36,7 @@ var expectedConf = &Config{
{
JobName: "prometheus",
HonorLabels: true,
ScrapeInterval: Duration(15 * time.Second),
ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout,

View File

@ -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).

View File

@ -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)
}

View File

@ -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.

View File

@ -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)

View File

@ -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()

View File

@ -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.

View File

@ -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.",

View File

@ -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
}

View File

@ -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">

108
web/federate.go Normal file
View File

@ -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
}

View File

@ -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 {