mirror of https://github.com/prometheus/prometheus
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
107 lines
2.8 KiB
107 lines
2.8 KiB
// Copyright 2020 The Prometheus Authors |
|
// Licensed under the Apache License, Version 2.0 (the "License"); |
|
// you may not use this file except in compliance with the License. |
|
// You may obtain a copy of the License at |
|
// |
|
// http://www.apache.org/licenses/LICENSE-2.0 |
|
// |
|
// Unless required by applicable law or agreed to in writing, software |
|
// distributed under the License is distributed on an "AS IS" BASIS, |
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
// See the License for the specific language governing permissions and |
|
// limitations under the License. |
|
|
|
package labels |
|
|
|
import ( |
|
"regexp" |
|
"regexp/syntax" |
|
"strings" |
|
) |
|
|
|
type FastRegexMatcher struct { |
|
re *regexp.Regexp |
|
prefix string |
|
suffix string |
|
contains string |
|
} |
|
|
|
func NewFastRegexMatcher(v string) (*FastRegexMatcher, error) { |
|
re, err := regexp.Compile("^(?:" + v + ")$") |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
parsed, err := syntax.Parse(v, syntax.Perl) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
m := &FastRegexMatcher{ |
|
re: re, |
|
} |
|
|
|
if parsed.Op == syntax.OpConcat { |
|
m.prefix, m.suffix, m.contains = optimizeConcatRegex(parsed) |
|
} |
|
|
|
return m, nil |
|
} |
|
|
|
func (m *FastRegexMatcher) MatchString(s string) bool { |
|
if m.prefix != "" && !strings.HasPrefix(s, m.prefix) { |
|
return false |
|
} |
|
if m.suffix != "" && !strings.HasSuffix(s, m.suffix) { |
|
return false |
|
} |
|
if m.contains != "" && !strings.Contains(s, m.contains) { |
|
return false |
|
} |
|
return m.re.MatchString(s) |
|
} |
|
|
|
func (m *FastRegexMatcher) GetRegexString() string { |
|
return m.re.String() |
|
} |
|
|
|
// optimizeConcatRegex returns literal prefix/suffix text that can be safely |
|
// checked against the label value before running the regexp matcher. |
|
func optimizeConcatRegex(r *syntax.Regexp) (prefix, suffix, contains string) { |
|
sub := r.Sub |
|
|
|
// We can safely remove begin and end text matchers respectively |
|
// at the beginning and end of the regexp. |
|
if len(sub) > 0 && sub[0].Op == syntax.OpBeginText { |
|
sub = sub[1:] |
|
} |
|
if len(sub) > 0 && sub[len(sub)-1].Op == syntax.OpEndText { |
|
sub = sub[:len(sub)-1] |
|
} |
|
|
|
if len(sub) == 0 { |
|
return |
|
} |
|
|
|
// Given Prometheus regex matchers are always anchored to the begin/end |
|
// of the text, if the first/last operations are literals, we can safely |
|
// treat them as prefix/suffix. |
|
if sub[0].Op == syntax.OpLiteral && (sub[0].Flags&syntax.FoldCase) == 0 { |
|
prefix = string(sub[0].Rune) |
|
} |
|
if last := len(sub) - 1; sub[last].Op == syntax.OpLiteral && (sub[last].Flags&syntax.FoldCase) == 0 { |
|
suffix = string(sub[last].Rune) |
|
} |
|
|
|
// If contains any literal which is not a prefix/suffix, we keep the |
|
// 1st one. We do not keep the whole list of literals to simplify the |
|
// fast path. |
|
for i := 1; i < len(sub)-1; i++ { |
|
if sub[i].Op == syntax.OpLiteral && (sub[i].Flags&syntax.FoldCase) == 0 { |
|
contains = string(sub[i].Rune) |
|
break |
|
} |
|
} |
|
|
|
return |
|
}
|
|
|