共计 4144 个字符,预计需要花费 11 分钟才能阅读完成。
一. 概述
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;
正文完
发表至: prometheus
2023-03-18