一.概述
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.gofunc (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.gofunc 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.gotype Builder struct { base Labels del []string add []Label}
当初始化Builder时,会将base中label value=""的增加到Builder.Del中:
// pkg/labels/labels.gofunc 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.gofunc (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.gofunc (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.gofunc (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;