refac: optimize CreatedTimestamp()

- Use refactored CreatedTimestamp function with bug fixes
 - Remove unused code in labels.go
 - Improve code documentation

 Signed-off-by: Manik Rana <manikrana54@gmail.com>

Signed-off-by: Manik Rana <manikrana54@gmail.com>
This commit is contained in:
Manik Rana 2024-07-10 19:46:18 +05:30
parent f62664a8e9
commit b225050b68
3 changed files with 59 additions and 112 deletions

View file

@ -373,29 +373,6 @@ func (ls Labels) ReleaseStrings(release func(string)) {
}
}
// ExtractNames returns an array of all Name in ls.
func (ls Labels) ExtractNames() []string {
names := make([]string, len(ls))
for i, label := range ls {
names[i] = label.Name
}
return names
}
// Method to check if the Labels contains any of the provided label Names
// returns true if any of the provided label names are present in the Labels
// return false if no Names are provided
func (ls Labels) Contains(names ...string) bool {
for _, name := range names {
for _, label := range ls {
if label.Name == name {
return true
}
}
}
return false
}
// Builder allows modifying Labels.
type Builder struct {
base Labels

View file

@ -889,45 +889,3 @@ func TestMarshaling(t *testing.T) {
require.NoError(t, err)
require.Equal(t, f, gotFY)
}
func TestExtractNames(t *testing.T) {
tests := []struct {
name string
labels Labels
expected []string
}{
{"Empty slice empty labels", Labels{}, []string{}},
{"Single label", Labels{{"hi", "Value1"}}, []string{"hi"}},
{"Multiple labels", Labels{{"hi", "Value1"}, {"yoyoy", "Value2"}, {"xxxx", "Value3"}}, []string{"hi", "yoyoy", "xxxx"}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.labels.ExtractNames()
require.Equal(t, tt.expected, got)
})
}
}
func TestContains(t *testing.T) {
tests := []struct {
name string
labels Labels
names []string
expected bool
}{
{"Empty slice", Labels{}, []string{"hi"}, false},
{"Empty slice Empty Labels", Labels{}, []string{}, false},
{"Empty slice non-Labels", Labels{{"hi", "Value1"}, {"yoyoy", "Value2"}}, []string{}, false},
{"Single match", Labels{{"hi", "Value1"}}, []string{"hi"}, true},
{"Multiple matches", Labels{{"hi", "Value1"}, {"yoyoy", "Value2"}, {"xxxx", "Value3"}}, []string{"hi", "bye"}, true},
{"No match", Labels{{"hi", "Value1"}, {"yoyoy", "Value2"}, {"xxxx", "Value3"}}, []string{"bye"}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.labels.Contains(tt.names...)
require.Equal(t, tt.expected, got)
})
}
}

View file

@ -226,58 +226,70 @@ func (p *OpenMetricsParser) Exemplar(e *exemplar.Exemplar) bool {
return true
}
// CreatedTimestamp returns the created timestamp as *int64 of a given Metric.
// It searches for the _created line of a given metric and uses an ephemeral createdTimestampParser to peek ahead and find the _created line.
// CreatedTimestamp returns the created timestamp for a current Metric if exists or nil.
// NOTE(Maniktherana): Might use additional CPU/mem resources due to deep copy of parser required for peeking given 1.0 OM specification on _created series.
func (p *OpenMetricsParser) CreatedTimestamp() *int64 {
var lbs labels.Labels
p.Metric(&lbs)
lbs = lbs.DropMetricName()
newParser := createdTimestampParser(p)
loop:
for {
switch t, _ := newParser.Next(); t {
case EntrySeries:
// TODO: potentially broken? Missing type?
if newParser.mName != p.mName {
if !typeRequiresCT(p.mtype) {
// Not a CT supported metric type, fast path.
return nil
}
var newLbs labels.Labels
newParser.Metric(&newLbs)
name := newLbs.Get(model.MetricNameLabel)
if !strings.HasSuffix(name, "_created") {
var (
currLset labels.Labels
buf []byte
peekWithoutNameLsetHash uint64
)
p.Metric(&currLset)
currWithoutNameLsetHash, buf := currLset.HashWithoutLabels(buf, labels.MetricName, "le", "quantile")
// Search for the _created line for the currName using ephemeral parser until
// we see EOF or new metric family. We have to do it as we don't know where (and if)
// that CT line is.
// TODO(bwplotka): Make sure OM 1.1/2.0 pass CT via metadata or exemplar-like to avoid this.
peek := deepCopy(p)
for {
eType, err := peek.Next()
if err != nil {
// This means p will give error too later on, so def no CT line found.
// This might result in partial scrape with wrong/missing CT, but only
// spec improvement would help.
// TODO(bwplotka): Make sure OM 1.1/2.0 pass CT via metadata or exemplar-like to avoid this.
return nil
}
if eType != EntrySeries {
// Assume we hit different family, no CT line found.
return nil
}
// We are sure this series is for sure the same metric family as in currLset
// because otherwise we would have EntryType first which we ruled out before.
var peekedLset labels.Labels
peek.Metric(&peekedLset)
peekedName := peekedLset.Get(model.MetricNameLabel)
if !strings.HasSuffix(peekedName, "_created") {
// Not a CT line, search more.
continue
}
newLbs = newLbs.DropMetricName()
switch p.mtype {
case model.MetricTypeCounter, model.MetricTypeHistogram, model.MetricTypeSummary:
labelDiffs := lbs.MatchLabels(false, newLbs.ExtractNames()...)
if labelDiffs.Len() != 0 {
if !labelDiffs.Contains("quantile", "le") || labelDiffs.Len() != 1 {
// We got a CT line here, but let's search if CT line is actually for our series, edge case.
peekWithoutNameLsetHash, buf = peekedLset.HashWithoutLabels(buf, labels.MetricName, "le", "quantile")
if peekWithoutNameLsetHash != currWithoutNameLsetHash {
// CT line for a different series, for our series no CT.
return nil
}
}
default:
// if mtype is not counter, summary or histogram, it won't have a valid _created line
return nil
}
ct := int64(newParser.val)
ct := int64(peek.val)
return &ct
}
}
func typeRequiresCT(t model.MetricType) bool {
switch t {
case model.MetricTypeCounter, model.MetricTypeSummary, model.MetricTypeHistogram:
return true
default:
break loop
return false
}
}
return nil
}
// createdTimestampParser creates a copy of a parser without re-using the slices' original memory addresses.
// The function `CreatedTimestamp()` uses a copy of the parser to "peek" at _created lines that might be several lines ahead, without changing the state of the original parser.
//
// Additionally, createdTimestampParser switches `skipCT` from true to false, because this new parser needs to return _created lines.
func createdTimestampParser(p *OpenMetricsParser) OpenMetricsParser {
func deepCopy(p *OpenMetricsParser) OpenMetricsParser {
newB := make([]byte, len(p.l.b))
copy(newB, p.l.b)