package scrape
import (
"errors"
"fmt"
"hash/fnv"
"net"
"net/url"
"strconv"
"strings"
"sync"
"time"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/config"
"github.com/prometheus/prometheus/discovery/targetgroup"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/model/relabel"
)
type TargetHealth string
const (
HealthUnknown TargetHealth = "unknown"
HealthGood TargetHealth = "up"
HealthBad TargetHealth = "down"
)
type Target struct {
discoveredLabels labels.Labels
labels labels.Labels
params url.Values
mtx sync.RWMutex
lastError error
lastScrape time.Time
lastScrapeDuration time.Duration
health TargetHealth
}
func NewTarget(labels, discoveredLabels labels.Labels, params url.Values) *Target {
return &Target{
labels: labels,
discoveredLabels: discoveredLabels,
params: params,
health: HealthUnknown,
}
}
func (t *Target) String() string {
return t.URL().String()
}
func (t *Target) hash() uint64 {
h := fnv.New64a()
_, _ = h.Write([]byte(fmt.Sprintf("%016d", t.Labels().Hash())))
_, _ = h.Write([]byte(t.URL().String()))
return h.Sum64()
}
func (t *Target) offset(interval time.Duration) time.Duration {
now := time.Now().UnixNano()
var (
base = now % int64(interval)
offset = t.hash() % uint64(interval)
next = base + int64(offset)
)
if next > int64(interval) {
next -= int64(interval)
}
return time.Duration(next)
}
func (t *Target) Params() url.Values {
q := make(url.Values, len(t.params))
for k, values := range t.params {
q[k] = make([]string, len(values))
copy(q[k], values)
}
return q
}
func (t *Target) Labels() labels.Labels {
lset := make(labels.Labels, 0, len(t.labels))
for _, l := range t.labels {
if !strings.HasPrefix(l.Name, model.ReservedLabelPrefix) {
lset = append(lset, l)
}
}
return lset
}
func (t *Target) DiscoveredLabels() labels.Labels {
t.mtx.RLock()
defer t.mtx.RUnlock()
lset := make(labels.Labels, len(t.discoveredLabels))
copy(lset, t.discoveredLabels)
return lset
}
func (t *Target) Clone() *Target {
return NewTarget(
t.Labels(),
t.DiscoveredLabels(),
t.Params(),
)
}
func (t *Target) SetDiscoveredLabels(l labels.Labels) {
t.mtx.Lock()
defer t.mtx.Unlock()
t.discoveredLabels = l
}
func (t *Target) URL() *url.URL {
params := url.Values{}
for k, v := range t.params {
params[k] = make([]string, len(v))
copy(params[k], v)
}
for _, l := range t.labels {
if !strings.HasPrefix(l.Name, model.ParamLabelPrefix) {
continue
}
ks := l.Name[len(model.ParamLabelPrefix):]
if len(params[ks]) > 0 {
params[ks][0] = l.Value
} else {
params[ks] = []string{l.Value}
}
}
return &url.URL{
Scheme: t.labels.Get(model.SchemeLabel),
Host: t.labels.Get(model.AddressLabel),
Path: t.labels.Get(ProfilePath),
RawQuery: params.Encode(),
}
}
func (t *Target) LastError() error {
t.mtx.RLock()
defer t.mtx.RUnlock()
return t.lastError
}
func (t *Target) LastScrape() time.Time {
t.mtx.RLock()
defer t.mtx.RUnlock()
return t.lastScrape
}
func (t *Target) LastScrapeDuration() time.Duration {
t.mtx.RLock()
defer t.mtx.RUnlock()
return t.lastScrapeDuration
}
func (t *Target) Health() TargetHealth {
t.mtx.RLock()
defer t.mtx.RUnlock()
return t.health
}
func LabelsByProfiles(lset labels.Labels, c *ProfilingConfig) []labels.Labels {
res := []labels.Labels{}
add := func(profileType string, cfgs ...ProfilingTarget) {
for _, p := range cfgs {
if p.Enabled {
l := lset.Copy()
l = append(l, labels.Label{Name: ProfilePath, Value: p.Path}, labels.Label{Name: ProfileName, Value: profileType})
res = append(res, l)
}
}
}
for profilingType, profilingConfig := range c.AllTargets() {
add(profilingType, profilingConfig)
}
return res
}
type Targets []*Target
func (ts Targets) Len() int { return len(ts) }
func (ts Targets) Less(i, j int) bool { return ts[i].URL().String() < ts[j].URL().String() }
func (ts Targets) Swap(i, j int) { ts[i], ts[j] = ts[j], ts[i] }
const (
ProfilePath = "__profile_path__"
ProfileName = "__name__"
ProfileTraceType = "trace"
)
func populateLabels(lset labels.Labels, cfg Arguments) (res, orig labels.Labels, err error) {
scrapeLabels := []labels.Label{
{Name: model.JobLabel, Value: cfg.JobName},
{Name: model.SchemeLabel, Value: cfg.Scheme},
}
lb := labels.NewBuilder(lset)
for _, l := range scrapeLabels {
if lv := lset.Get(l.Name); lv == "" {
lb.Set(l.Name, l.Value)
}
}
for k, v := range cfg.Params {
if len(v) > 0 {
lb.Set(model.ParamLabelPrefix+k, v[0])
}
}
preRelabelLabels := lb.Labels(nil)
lset, keep := relabel.Process(preRelabelLabels)
if !keep {
return nil, preRelabelLabels, nil
}
if v := lset.Get(model.AddressLabel); v == "" {
return nil, nil, errors.New("no address")
}
if v := lset.Get(model.AddressLabel); v == "" {
return nil, nil, fmt.Errorf("no address")
}
lb = labels.NewBuilder(lset)
addPort := func(s string) bool {
if _, _, err := net.SplitHostPort(s); err == nil {
return false
}
_, _, err := net.SplitHostPort(s + ":1234")
return err == nil
}
addr := lset.Get(model.AddressLabel)
if addPort(addr) {
switch lset.Get(model.SchemeLabel) {
case "http", "":
addr = addr + ":80"
case "https":
addr = addr + ":443"
default:
return nil, nil, fmt.Errorf("invalid scheme: %q", cfg.Scheme)
}
lb.Set(model.AddressLabel, addr)
}
if err := config.CheckTargetAddress(model.LabelValue(addr)); err != nil {
return nil, nil, err
}
for _, l := range lset {
if strings.HasPrefix(l.Name, model.MetaLabelPrefix) {
lb.Del(l.Name)
}
}
if v := lset.Get(model.InstanceLabel); v == "" {
lb.Set(model.InstanceLabel, addr)
}
res = lb.Labels(nil)
for _, l := range res {
if !model.LabelValue(l.Value).IsValid() {
return nil, nil, fmt.Errorf("invalid label value for %q: %q", l.Name, l.Value)
}
}
return res, lset, nil
}
func targetsFromGroup(group *targetgroup.Group, cfg Arguments) ([]*Target, []*Target, error) {
var (
targets = make([]*Target, 0, len(group.Targets))
droppedTargets = make([]*Target, 0, len(group.Targets))
)
for i, tlset := range group.Targets {
lbls := make([]labels.Label, 0, len(tlset)+len(group.Labels))
for ln, lv := range tlset {
lbls = append(lbls, labels.Label{Name: string(ln), Value: string(lv)})
}
for ln, lv := range group.Labels {
if _, ok := tlset[ln]; !ok {
lbls = append(lbls, labels.Label{Name: string(ln), Value: string(lv)})
}
}
lset := labels.New(lbls...)
lsets := LabelsByProfiles(lset, &cfg.ProfilingConfig)
for _, lset := range lsets {
var profType string
for _, label := range lset {
if label.Name == ProfileName {
profType = label.Value
}
}
lbls, origLabels, err := populateLabels(lset, cfg)
if err != nil {
return nil, nil, fmt.Errorf("instance %d in group %s: %s", i, group, err)
}
if lbls == nil && origLabels != nil {
params := cfg.Params
if params == nil {
params = url.Values{}
}
lbls = append(lbls, labels.Label{Name: model.AddressLabel, Value: lset.Get(model.AddressLabel)})
lbls = append(lbls, labels.Label{Name: model.SchemeLabel, Value: cfg.Scheme})
lbls = append(lbls, labels.Label{Name: ProfilePath, Value: lset.Get(ProfilePath)})
for k, v := range cfg.Params {
if len(v) > 0 {
lbls = append(lbls, labels.Label{Name: model.ParamLabelPrefix + k, Value: v[0]})
}
}
droppedTargets = append(droppedTargets, NewTarget(lbls, origLabels, params))
continue
}
if lbls != nil || origLabels != nil {
params := cfg.Params
if params == nil {
params = url.Values{}
}
if pcfg, found := cfg.ProfilingConfig.AllTargets()[profType]; found && pcfg.Delta {
params.Add("seconds", strconv.Itoa(int((cfg.ScrapeInterval)/time.Second)-1))
}
targets = append(targets, NewTarget(lbls, origLabels, params))
}
}
}
return targets, droppedTargets, nil
}