Move template functionality out of target.

The target implementation and interface contain methods only serving a
specific purpose of the templates. They were moved to the template
as they operate on more fundamental target data.
This commit is contained in:
Fabian Reinartz 2015-05-18 12:16:25 +02:00
parent dbc08d390e
commit 1a2d57b45c
5 changed files with 59 additions and 70 deletions

View File

@ -19,7 +19,6 @@ import (
"math/rand" "math/rand"
"net/http" "net/http"
"net/url" "net/url"
"os"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -53,8 +52,6 @@ const (
var ( var (
errIngestChannelFull = errors.New("ingestion channel full") errIngestChannelFull = errors.New("ingestion channel full")
localhostRepresentations = []string{"127.0.0.1", "localhost"}
targetIntervalLength = prometheus.NewSummaryVec( targetIntervalLength = prometheus.NewSummaryVec(
prometheus.SummaryOpts{ prometheus.SummaryOpts{
Namespace: namespace, Namespace: namespace,
@ -118,17 +115,11 @@ type Target interface {
URL() string URL() string
// Used to populate the `instance` label in metrics. // Used to populate the `instance` label in metrics.
InstanceIdentifier() string InstanceIdentifier() string
// The URL as seen from other hosts. References to localhost are resolved
// to the address of the prometheus server.
GlobalURL() string
// Return the labels describing the targets. These are the base labels // Return the labels describing the targets. These are the base labels
// as well as internal labels. // as well as internal labels.
Labels() clientmodel.LabelSet fullLabels() clientmodel.LabelSet
// Return the target's base labels. // Return the target's base labels.
BaseLabels() clientmodel.LabelSet BaseLabels() clientmodel.LabelSet
// Return the target's base labels without job and instance label. That's
// useful for display purposes.
BaseLabelsWithoutJobAndInstance() clientmodel.LabelSet
// Start scraping the target in regular intervals. // Start scraping the target in regular intervals.
RunScraper(storage.SampleAppender) RunScraper(storage.SampleAppender)
// Stop scraping, synchronous. // Stop scraping, synchronous.
@ -195,7 +186,6 @@ type target struct {
// The status object for the target. It is only set once on initialization. // The status object for the target. It is only set once on initialization.
status *TargetStatus status *TargetStatus
// The HTTP client used to scrape the target's endpoint. // The HTTP client used to scrape the target's endpoint.
httpClient *http.Client httpClient *http.Client
@ -419,66 +409,43 @@ func (t *target) InstanceIdentifier() string {
return t.url.Host return t.url.Host
} }
// GlobalURL implements Target. // fullLabels implements Target.
func (t *target) GlobalURL() string { func (t *target) fullLabels() clientmodel.LabelSet {
url := t.URL()
hostname, err := os.Hostname()
if err != nil {
glog.Warningf("Couldn't get hostname: %s, returning target.URL()", err)
return url
}
for _, localhostRepresentation := range localhostRepresentations {
url = strings.Replace(url, "//"+localhostRepresentation, "//"+hostname, 1)
}
return url
}
// Labels implements Target.
func (t *target) Labels() clientmodel.LabelSet {
t.RLock() t.RLock()
defer t.RUnlock() defer t.RUnlock()
ls := clientmodel.LabelSet{} lset := make(clientmodel.LabelSet, len(t.baseLabels)+2)
for ln, lv := range t.baseLabels { for ln, lv := range t.baseLabels {
ls[ln] = lv lset[ln] = lv
} }
ls[clientmodel.MetricsPathLabel] = clientmodel.LabelValue(t.url.Path) lset[clientmodel.MetricsPathLabel] = clientmodel.LabelValue(t.url.Path)
ls[clientmodel.AddressLabel] = clientmodel.LabelValue(t.url.Host) lset[clientmodel.AddressLabel] = clientmodel.LabelValue(t.url.Host)
return ls return lset
} }
// BaseLabels implements Target. // BaseLabels implements Target.
func (t *target) BaseLabels() clientmodel.LabelSet { func (t *target) BaseLabels() clientmodel.LabelSet {
t.RLock() t.RLock()
defer t.RUnlock() defer t.RUnlock()
return t.baseLabels lset := make(clientmodel.LabelSet, len(t.baseLabels))
}
// BaseLabelsWithoutJobAndInstance implements Target.
//
// TODO(fabxc): This method does not have to be part of the interface. Implement this
// as a template filter func for the single use case.
func (t *target) BaseLabelsWithoutJobAndInstance() clientmodel.LabelSet {
t.RLock()
defer t.RUnlock()
ls := clientmodel.LabelSet{}
for ln, lv := range t.baseLabels { for ln, lv := range t.baseLabels {
if ln != clientmodel.JobLabel && ln != clientmodel.InstanceLabel { lset[ln] = lv
ls[ln] = lv
} }
} return lset
return ls
} }
func (t *target) recordScrapeHealth(sampleAppender storage.SampleAppender, timestamp clientmodel.Timestamp, scrapeDuration time.Duration) { func (t *target) recordScrapeHealth(sampleAppender storage.SampleAppender, timestamp clientmodel.Timestamp, scrapeDuration time.Duration) {
healthMetric := clientmodel.Metric{} t.RLock()
durationMetric := clientmodel.Metric{} healthMetric := make(clientmodel.Metric, len(t.baseLabels)+1)
for label, value := range t.BaseLabels() { durationMetric := make(clientmodel.Metric, len(t.baseLabels)+1)
healthMetric[clientmodel.MetricNameLabel] = clientmodel.LabelValue(scrapeHealthMetricName)
durationMetric[clientmodel.MetricNameLabel] = clientmodel.LabelValue(scrapeDurationMetricName)
for label, value := range t.baseLabels {
healthMetric[label] = value healthMetric[label] = value
durationMetric[label] = value durationMetric[label] = value
} }
healthMetric[clientmodel.MetricNameLabel] = clientmodel.LabelValue(scrapeHealthMetricName) t.RUnlock()
durationMetric[clientmodel.MetricNameLabel] = clientmodel.LabelValue(scrapeDurationMetricName)
healthValue := clientmodel.SampleValue(0) healthValue := clientmodel.SampleValue(0)
if t.status.State() == Healthy { if t.status.State() == Healthy {

View File

@ -44,13 +44,6 @@ func TestBaseLabels(t *testing.T) {
if !reflect.DeepEqual(want, got) { if !reflect.DeepEqual(want, got) {
t.Errorf("want base labels %v, got %v", want, got) t.Errorf("want base labels %v, got %v", want, got)
} }
delete(want, clientmodel.JobLabel)
delete(want, clientmodel.InstanceLabel)
got = target.BaseLabelsWithoutJobAndInstance()
if !reflect.DeepEqual(want, got) {
t.Errorf("want base labels %v, got %v", want, got)
}
} }
func TestTargetScrapeUpdatesState(t *testing.T) { func TestTargetScrapeUpdatesState(t *testing.T) {

View File

@ -215,7 +215,7 @@ func (tm *TargetManager) updateTargetGroup(tgroup *config.TargetGroup, cfg *conf
// to build up. // to build up.
wg.Add(1) wg.Add(1)
go func(t Target) { go func(t Target) {
match.Update(cfg, t.Labels()) match.Update(cfg, t.fullLabels())
wg.Done() wg.Done()
}(tnew) }(tnew)
newTargets[i] = match newTargets[i] = match

View File

@ -48,7 +48,7 @@
{{range $pool}} {{range $pool}}
<tr> <tr>
<td> <td>
<a href="{{.GlobalURL}}">{{.URL}}</a> <a href="{{.URL | globalURL}}">{{.URL}}</a>
</td> </td>
<td> <td>
<span class="alert alert-{{index $stateToClass .Status.State}} target_status_alert"> <span class="alert alert-{{index $stateToClass .Status.State}} target_status_alert">
@ -56,7 +56,7 @@
</span> </span>
</td> </td>
<td> <td>
{{.BaseLabelsWithoutJobAndInstance}} {{stripLabels .BaseLabels "job" "instance"}}
</td> </td>
<td> <td>
{{if .Status.LastScrape.IsZero}}Never{{else}}{{since .Status.LastScrape}} ago{{end}} {{if .Status.LastScrape.IsZero}}Never{{else}}{{since .Status.LastScrape}} ago{{end}}

View File

@ -29,10 +29,14 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
clientmodel "github.com/prometheus/client_golang/model"
"github.com/prometheus/prometheus/web/api" "github.com/prometheus/prometheus/web/api"
"github.com/prometheus/prometheus/web/blob" "github.com/prometheus/prometheus/web/blob"
) )
var localhostRepresentations = []string{"127.0.0.1", "localhost"}
// Commandline flags. // Commandline flags.
var ( var (
listenAddress = flag.String("web.listen-address", ":9090", "Address to listen on for the web interface, API, and telemetry.") listenAddress = flag.String("web.listen-address", ":9090", "Address to listen on for the web interface, API, and telemetry.")
@ -150,28 +154,53 @@ func getConsoles(pathPrefix string) string {
return "" return ""
} }
func getTemplate(name string, pathPrefix string) (t *template.Template, err error) { func getTemplate(name string, pathPrefix string) (*template.Template, error) {
t = template.New("_base") t := template.New("_base")
var err error
t.Funcs(template.FuncMap{ t.Funcs(template.FuncMap{
"since": time.Since, "since": time.Since,
"getConsoles": func() string { return getConsoles(pathPrefix) }, "getConsoles": func() string { return getConsoles(pathPrefix) },
"pathPrefix": func() string { return pathPrefix }, "pathPrefix": func() string { return pathPrefix },
"stripLabels": func(lset clientmodel.LabelSet, labels ...clientmodel.LabelName) clientmodel.LabelSet {
for _, ln := range labels {
delete(lset, ln)
}
return lset
},
"globalURL": func(url string) string {
hostname, err := os.Hostname()
if err != nil {
glog.Warningf("Couldn't get hostname: %s, returning target.URL()", err)
return url
}
for _, localhostRepresentation := range localhostRepresentations {
url = strings.Replace(url, "//"+localhostRepresentation, "//"+hostname, 1)
}
return url
},
}) })
file, err := getTemplateFile("_base") file, err := getTemplateFile("_base")
if err != nil { if err != nil {
glog.Error("Could not read base template: ", err) glog.Errorln("Could not read base template:", err)
return nil, err return nil, err
} }
t.Parse(file) t, err = t.Parse(file)
if err != nil {
glog.Errorln("Could not parse base template:", err)
}
file, err = getTemplateFile(name) file, err = getTemplateFile(name)
if err != nil { if err != nil {
glog.Error("Could not read base template: ", err) glog.Error("Could not read template %d: ", name, err)
return nil, err return nil, err
} }
t.Parse(file) t, err = t.Parse(file)
return if err != nil {
glog.Errorf("Could not parse template %s: %s", name, err)
}
return t, err
} }
func executeTemplate(w http.ResponseWriter, name string, data interface{}, pathPrefix string) { func executeTemplate(w http.ResponseWriter, name string, data interface{}, pathPrefix string) {