Skip to content

Commit ef4e937

Browse files
authored
perf(prometheus): optimize duplicate metric splitting to O(N) complexity (#1383)
1 parent bcf60e2 commit ef4e937

File tree

1 file changed

+45
-13
lines changed

1 file changed

+45
-13
lines changed

parser/prometheus/parser.go

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -87,30 +87,62 @@ func (p *Parser) Parse(buf []byte, slist *types.SampleList) error {
8787
totalLen := len(buf)
8888

8989
for offset < totalLen {
90-
// Find next delimiter position relative to current offset
91-
relIdxHelp := bytes.Index(buf[offset:], helpHeader)
92-
relIdxType := bytes.Index(buf[offset:], typeHeader)
90+
// Find next delimiter starting strictly AFTER 'offset'
91+
// We use IndexByte ('#') for performance (O(N) total scan instead of O(N^2))
92+
// and then verify if it's actually a header.
93+
94+
rest := buf[offset:]
95+
96+
// If we are at the very beginning of a block (or file), we might be standing ON a delimiter.
97+
// If so, we need to skip it to find the NEXT one.
98+
searchStart := 0
99+
if bytes.HasPrefix(rest, helpHeader) {
100+
searchStart = len(helpHeader)
101+
} else if bytes.HasPrefix(rest, typeHeader) {
102+
searchStart = len(typeHeader)
103+
}
93104

94-
var relIdx int
105+
idx := -1
106+
scanOffset := searchStart
95107

96-
if relIdxHelp == -1 && relIdxType == -1 {
97-
// No more delimiters, take the rest
98-
relIdx = totalLen - offset
99-
} else if relIdxHelp != -1 && (relIdxType == -1 || relIdxHelp < relIdxType) {
100-
relIdx = relIdxHelp
101-
} else {
102-
relIdx = relIdxType
108+
for {
109+
// Fast scan for '#'
110+
p := bytes.IndexByte(rest[scanOffset:], '#')
111+
if p == -1 {
112+
idx = -1
113+
break
114+
}
115+
116+
// Potential delimiter found at relative index (scanOffset + p)
117+
candidate := rest[scanOffset+p:]
118+
119+
// Verify if it is indeed a header
120+
if bytes.HasPrefix(candidate, helpHeader) || bytes.HasPrefix(candidate, typeHeader) {
121+
idx = scanOffset + p
122+
break
123+
}
124+
125+
// False alarm (e.g. '#' inside a label or comment), continue scanning
126+
scanOffset += p + 1
103127
}
104128

105129
// Calculate absolute end of current chunk
130+
var relIdx int
131+
if idx == -1 {
132+
relIdx = len(rest)
133+
} else {
134+
relIdx = idx
135+
}
136+
106137
chunkEnd := offset + relIdx
107138

139+
// Process current chunk if not empty
108140
if chunkEnd > offset {
109141
chunk := buf[offset:chunkEnd]
110142
// Trim only leading/trailing whitespace to check for empty content
111143
if len(bytes.TrimSpace(chunk)) > 0 {
112144
// Handle "info" type check and replacement for each chunk
113-
// "info->gauge" replacement feature
145+
// This ensures we support the legacy "info->gauge" replacement feature
114146
// We do Contains check first to avoid allocation if replacement isn't needed (Zero-Copy path)
115147
if bytes.Contains(chunk, infoBytes) {
116148
chunk = bytes.ReplaceAll(chunk, infoBytes, gaugeBytes)
@@ -171,4 +203,4 @@ func getNameAndValue(m *dto.Metric, metricName string) map[string]interface{} {
171203
}
172204
}
173205
return fields
174-
}
206+
}

0 commit comments

Comments
 (0)