一.概述
prometheus拉取exporter中的指标进行解析时,对于labels,若label value=””,则会将该label去掉;
也就是说,对于label value=””,不会存储到tsdb,通过prom API也查询不到该label。
二.源码分析
1.原理
scape以后,会经过两次删除,将value=””的Label删掉:
- 第一次:对拉取的text进行label解析后,在添加target的label时,删除value=””的label;
- 第二次:将解析后的labels、t/v数据,存储到head时,删除value=””的label;
// scrape/scrape.go
func (sl *scrapeLoop) append(app storage.Appender, b []byte, contentType string, ts time.Time) (total, added, seriesAdded int, err error) {
...
for {
var (
et textparse.Entry
sampleAdded bool
)
if et, err = p.Next(); err != nil {
if err == io.EOF {
err = nil
}
break
}
...
met, tp, v := p.Series()
ce, ok := sl.cache.get(yoloString(met)) // 查找cache
if !ok {
var lset labels.Labels
mets := p.Metric(&lset) // 解析原始labels,保存到lset
lset = sl.sampleMutator(lset) // 添加target的label,含第一次删除的逻辑
...
ref, err = app.Add(lset, t, v) // 添加到head,含第二次删除的逻辑
}
}
...
}
2.第一次删除:解析labels
scrape以后,会进行拉取对象的文本解析;
解析label时,会添加target的label,此时会删除value=””的label;
执行过程:
- 首先,构造Builder对象,删除value=“”的label由Builder对象完成;
-
然后,根据honor=true/false,处理与target.label的冲突;
- 删除value=””的label的操作,在Builder.Set(lkey, lvalue)中;
- 最后,执行relabel操作;
// scrape/scrape.go
func mutateSampleLabels(lset labels.Labels, target *Target, honor bool, rc []*relabel.Config) labels.Labels {
lb := labels.NewBuilder(lset) // 使用Builder对象进行构造
if honor { // honor=true时,处理与target.label的冲突,当labelKey冲突时,直接使用scrape的label,不顾及target的label
for _, l := range target.Labels() {
if !lset.Has(l.Name) {
lb.Set(l.Name, l.Value)
}
}
} else { // 默认honor=false时,处理与target.label的冲突,当labelKey冲突时,将scrape的label修改为:exported_labelKey,lableValue
for _, l := range target.Labels() {
// existingValue will be empty if l.Name doesn't exist.
existingValue := lset.Get(l.Name)
if existingValue != "" {
lb.Set(model.ExportedLabelPrefix+l.Name, existingValue)
}
// It is now safe to set the target label.
lb.Set(l.Name, l.Value)
}
}
res := lb.Labels() // 输出Builder构造完成的Labels
if len(rc) > 0 { // 执行relabel操作
res = relabel.Process(res, rc...)
}
return res
}
1).Builder对象
- base:scrape过来的原始labels;
- add: 添加的target的label;
- del: 删除的Labels;
// pkg/labels/labels.go
type Builder struct {
base Labels
del []string
add []Label
}
当初始化Builder时,会将base中label value=””的添加到Builder.Del中:
// pkg/labels/labels.go
func NewBuilder(base Labels) *Builder {
b := &Builder{
del: make([]string, 0, 5),
add: make([]Label, 0, 5),
}
b.Reset(base)
return b
}
func (b *Builder) Reset(base Labels) {
b.base = base
b.del = b.del[:0]
b.add = b.add[:0]
for _, l := range b.base {
if l.Value == "" { // label value=""时,添加到Builder.Del
b.del = append(b.del, l.Name)
}
}
}
2).根据Honor处理与target.label的冲突
- honor=true时,处理与target.label的冲突,当labelKey冲突时,直接使用scrape的label,不顾及target的label;
- honor=false时,处理与target.label的冲突,当labelKey冲突时,将scrape的label修改为:exported_labelKey,lableValue;
- 默认honor=false;
3).Builder.Set(lkey,lvalue)删除value=””的label
value=””时,将其添加到Builder.Del中;
// pkg/labels/labels.go
func (b *Builder) Set(n, v string) *Builder {
if v == "" {
// Empty labels are the same as missing labels.
return b.Del(n)
}
for i, a := range b.add {
if a.Name == n {
b.add[i].Value = v
return b
}
}
b.add = append(b.add, Label{Name: n, Value: v})
return b
}
4).输出Builder构造完成的Labels
输出时,将排除掉Builder.Del中的label:
// pkg/labels/labels.go
func (b *Builder) Labels() Labels {
...
// In the general case, labels are removed, modified or moved
// rather than added.
res := make(Labels, 0, len(b.base))
Outer:
for _, l := range b.base {
for _, n := range b.del { // 去掉b.Del
if l.Name == n {
continue Outer
}
}
...
res = append(res, l)
}
res = append(res, b.add...)
sort.Sort(res)
return res
}
3.第二次删除:
labels、t/v数据准备完毕后,会将labels、t/v添加到head中:
// tsdb/head.go
func (a *headAppender) Add(lset labels.Labels, t int64, v float64) (uint64, error) {
...
// Ensure no empty labels have gotten through.
lset = lset.WithoutEmpty() // 这里删除value=""的label
...
s, created, err := a.head.getOrCreate(lset.Hash(), lset)
...
if created {
a.series = append(a.series, record.RefSeries{
Ref: s.ref,
Labels: lset,
})
}
return s.ref, a.AddFast(s.ref, t, v)
}
删除value=””的label的代码,其实现方式:
- 一般的做法是,新建Lables对象,遍历老labels对象,将value!=””的label添加进去;
-
下面的代码使用了相对内存高效的做法:
- 绝大多数场景下,不存在value=””的label,直接返回原labelset;
- 若发现value=””的label,则新建Labels对象,将value!=””的label添加进去;
// pkg/labels/labels.go
// WithoutEmpty returns the labelset without empty labels.
// May return the same labelset.
func (ls Labels) WithoutEmpty() Labels {
for _, v := range ls {
if v.Value != "" { // 判断value是否""
continue
}
els := make(Labels, 0, len(ls)-1)
for _, v := range ls {
if v.Value != "" { // 判断value是否""
els = append(els, v)
}
}
return els
}
return ls
}
三.总结
prometheus会删除value=””的label,也不会存入TSDB,通过以下方面实现这一点:
-
解析完成scrape的label后,为series添加target的label时,会过滤掉value=””的label;
- 通过Builder实现;
- 拿到完整的lables后,存入TSDB前,再次过滤掉value=””的label;
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net