@@ -14,6 +14,7 @@ import (
1414 "github.com/cloudflare/pint/internal/discovery"
1515 "github.com/cloudflare/pint/internal/output"
1616 "github.com/cloudflare/pint/internal/parser"
17+ "github.com/cloudflare/pint/internal/parser/utils"
1718 "github.com/cloudflare/pint/internal/promapi"
1819
1920 "github.com/prometheus/common/model"
@@ -120,10 +121,10 @@ func (c SeriesCheck) Check(ctx context.Context, _ discovery.Path, rule parser.Ru
120121
121122 params := promapi .NewRelativeRange (settings .lookbackRangeDuration , settings .lookbackStepDuration )
122123
123- selectors := getSelectors (expr .Query )
124+ selectors := utils . HasVectorSelector (expr .Query )
124125
125126 done := map [string ]bool {}
126- for _ , selector := range selectors {
127+ for _ , selector := range getNonFallbackSelectors ( expr . Query ) {
127128 if _ , ok := done [selector .String ()]; ok {
128129 continue
129130 }
@@ -539,12 +540,12 @@ func (c SeriesCheck) Check(ctx context.Context, _ discovery.Path, rule parser.Ru
539540 problems = append (problems , Problem {
540541 Lines : expr .Value .Lines ,
541542 Reporter : c .Reporter (),
542- Text : fmt .Sprintf ("pint %s comment `%s` check doesn't match any selector in this query" , comments .DisableComment , disable .Match ),
543+ Text : fmt .Sprintf ("pint %s comment `%s` doesn't match any selector in this query" , comments .DisableComment , disable .Match ),
543544 Details : SeriesCheckUnusedDisableComment ,
544545 Severity : Warning ,
545546 })
546547 }
547- for _ , ruleSet := range orphanedRuleSetComments (c . Reporter (), rule , selectors ) {
548+ for _ , ruleSet := range orphanedRuleSetComments (rule , selectors ) {
548549 problems = append (problems , Problem {
549550 Lines : expr .Value .Lines ,
550551 Reporter : c .Reporter (),
@@ -635,39 +636,49 @@ func (c SeriesCheck) instantSeriesCount(ctx context.Context, query string) (int,
635636
636637func (c SeriesCheck ) getMinAge (rule parser.Rule , selector promParser.VectorSelector ) (minAge time.Duration , problems []Problem ) {
637638 minAge = time .Hour * 2
638- prefixes := ruleSetMinAgePrefixes (c .Reporter (), selector )
639639 for _ , ruleSet := range comments .Only [comments.RuleSet ](rule .Comments , comments .RuleSetType ) {
640- for _ , prefix := range prefixes {
641- if ! strings .HasPrefix (ruleSet .Value , prefix ) {
640+ matcher , key , value := parseRuleSet (ruleSet .Value )
641+ if key != "min-age" {
642+ continue
643+ }
644+ if matcher != "" {
645+ isMatch , _ := matchSelectorToMetric (selector , matcher )
646+ if ! isMatch {
642647 continue
643648 }
644- val := strings .TrimPrefix (ruleSet .Value , prefix )
645- dur , err := model .ParseDuration (val )
646- if err != nil {
647- problems = append (problems , Problem {
648- Lines : rule .Lines ,
649- Reporter : c .Reporter (),
650- Text : fmt .Sprintf ("Failed to parse pint comment as duration: %s" , err ),
651- Details : SeriesCheckMinAgeDetails ,
652- Severity : Warning ,
653- })
654- } else {
655- minAge = time .Duration (dur )
656- }
649+ }
650+ dur , err := model .ParseDuration (value )
651+ if err != nil {
652+ problems = append (problems , Problem {
653+ Lines : rule .Lines ,
654+ Reporter : c .Reporter (),
655+ Text : fmt .Sprintf ("Failed to parse pint comment as duration: %s" , err ),
656+ Details : SeriesCheckMinAgeDetails ,
657+ Severity : Warning ,
658+ })
659+ } else {
660+ minAge = time .Duration (dur )
657661 }
658662 }
659663
660664 return minAge , problems
661665}
662666
663667func (c SeriesCheck ) isLabelValueIgnored (rule parser.Rule , selector promParser.VectorSelector , labelName string ) bool {
664- values := ruleSetIgnoreLabelValValues (c .Reporter (), labelName , selector )
665668 for _ , ruleSet := range comments .Only [comments.RuleSet ](rule .Comments , comments .RuleSetType ) {
666- for _ , val := range values {
667- if ruleSet .Value == val {
668- return true
669+ matcher , key , value := parseRuleSet (ruleSet .Value )
670+ if key != "ignore/label-value" {
671+ continue
672+ }
673+ if matcher != "" {
674+ isMatch , _ := matchSelectorToMetric (selector , matcher )
675+ if ! isMatch {
676+ continue
669677 }
670678 }
679+ if labelName == value {
680+ return true
681+ }
671682 }
672683 return false
673684}
@@ -686,7 +697,7 @@ func (c SeriesCheck) textAndSeverity(settings *PromqlSeriesSettings, name, text
686697 return text , s
687698}
688699
689- func getSelectors (n * parser.PromQLNode ) (selectors []promParser.VectorSelector ) {
700+ func getNonFallbackSelectors (n * parser.PromQLNode ) (selectors []promParser.VectorSelector ) {
690701LOOP:
691702 for _ , vs := range parser.WalkDownExpr [* promParser.VectorSelector ](n ) {
692703 for _ , bin := range parser.WalkUpExpr [* promParser.BinaryExpr ](vs .Parent ) {
@@ -735,26 +746,12 @@ func isDisabled(rule parser.Rule, selector promParser.VectorSelector) bool {
735746 for _ , disable := range comments .Only [comments.Disable ](rule .Comments , comments .DisableType ) {
736747 if strings .HasPrefix (disable .Match , SeriesCheckName + "(" ) && strings .HasSuffix (disable .Match , ")" ) {
737748 cs := strings .TrimSuffix (strings .TrimPrefix (disable .Match , SeriesCheckName + "(" ), ")" )
738- // try full string or name match first
739- if cs == selector .String () || cs == selector .Name {
740- return true
741- }
742- // then try matchers
743- m , err := promParser .ParseMetricSelector (cs )
744- if err != nil {
749+ isMatch , ok := matchSelectorToMetric (selector , cs )
750+ if ! ok {
745751 continue
746752 }
747- for _ , l := range m {
748- var isMatch bool
749- for _ , s := range selector .LabelMatchers {
750- if s .Type == l .Type && s .Name == l .Name && s .Value == l .Value {
751- isMatch = true
752- break
753- }
754- }
755- if ! isMatch {
756- goto NEXT
757- }
753+ if ! isMatch {
754+ goto NEXT
758755 }
759756 return true
760757 }
@@ -763,6 +760,48 @@ func isDisabled(rule parser.Rule, selector promParser.VectorSelector) bool {
763760 return false
764761}
765762
763+ func matchSelectorToMetric (selector promParser.VectorSelector , metric string ) (bool , bool ) {
764+ // Try full string or name match first.
765+ if metric == selector .String () || metric == selector .Name {
766+ return true , true
767+ }
768+ // Then try matchers.
769+ m , err := promParser .ParseMetricSelector (metric )
770+ if err != nil {
771+ // Ignore errors
772+ return false , false
773+ }
774+ for _ , l := range m {
775+ var isMatch bool
776+ for _ , s := range selector .LabelMatchers {
777+ if s .Type == l .Type && s .Name == l .Name && s .Value == l .Value {
778+ return true , true
779+ }
780+ }
781+ if ! isMatch {
782+ return false , true
783+ }
784+ }
785+ return false , true
786+ }
787+
788+ func parseRuleSet (s string ) (matcher , key , value string ) {
789+ if strings .HasPrefix (s , SeriesCheckName + "(" ) {
790+ matcher = strings .TrimPrefix (s [:strings .LastIndex (s , ")" )], SeriesCheckName + "(" )
791+ s = s [strings .LastIndex (s , ")" )+ 1 :]
792+ } else {
793+ s = strings .TrimPrefix (s , SeriesCheckName )
794+ }
795+ parts := strings .Fields (s )
796+ if len (parts ) > 0 {
797+ key = parts [0 ]
798+ }
799+ if len (parts ) > 1 {
800+ value = strings .Join (parts [1 :], " " )
801+ }
802+ return matcher , key , value
803+ }
804+
766805func orphanedDisableComments (ctx context.Context , rule parser.Rule , selectors []promParser.VectorSelector ) (orhpaned []comments.Disable ) {
767806 var promNames , promTags []string
768807 if val := ctx .Value (promapi .AllPrometheusServers ); val != nil {
@@ -787,32 +826,13 @@ func orphanedDisableComments(ctx context.Context, rule parser.Rule, selectors []
787826 continue
788827 }
789828 for _ , selector := range selectors {
790- // try full string or name match first
791- if match == selector .String () || match == selector .Name {
792- wasUsed = true
793- goto NEXT
794- }
795- // then try matchers
796- m , err := promParser .ParseMetricSelector (match )
797- if err != nil {
829+ isMatch , ok := matchSelectorToMetric (selector , match )
830+ if ! ok {
798831 continue
799832 }
800- var isMismatch bool
801- for _ , l := range m {
802- var isMatch bool
803- for _ , s := range selector .LabelMatchers {
804- if s .Type == l .Type && s .Name == l .Name && s .Value == l .Value {
805- isMatch = true
806- break
807- }
808- }
809- if ! isMatch {
810- isMismatch = true
811- break
812- }
813- }
814- if ! isMismatch {
833+ if isMatch {
815834 wasUsed = true
835+ goto NEXT
816836 }
817837 }
818838 NEXT:
@@ -823,45 +843,31 @@ func orphanedDisableComments(ctx context.Context, rule parser.Rule, selectors []
823843 return orhpaned
824844}
825845
826- func ruleSetIgnoreLabelValValues (reporter , labelName string , selector promParser.VectorSelector ) []string {
827- bareSelector := stripLabels (selector )
828- return []string {
829- fmt .Sprintf ("%s ignore/label-value %s" , reporter , labelName ),
830- fmt .Sprintf ("%s(%s) ignore/label-value %s" , reporter , bareSelector .String (), labelName ),
831- fmt .Sprintf ("%s(%s) ignore/label-value %s" , reporter , selector .String (), labelName ),
832- }
833- }
834-
835- func ruleSetMinAgePrefixes (reporter string , selector promParser.VectorSelector ) []string {
836- bareSelector := stripLabels (selector )
837- return []string {
838- reporter + " min-age " ,
839- fmt .Sprintf ("%s(%s) min-age " , reporter , bareSelector .String ()),
840- fmt .Sprintf ("%s(%s) min-age " , reporter , selector .String ()),
841- }
842- }
843-
844- func orphanedRuleSetComments (reporter string , rule parser.Rule , selectors []promParser.VectorSelector ) (orhpaned []comments.RuleSet ) {
846+ func orphanedRuleSetComments (rule parser.Rule , selectors []promParser.VectorSelector ) (orhpaned []comments.RuleSet ) {
845847 for _ , ruleSet := range comments .Only [comments.RuleSet ](rule .Comments , comments .RuleSetType ) {
846- var used bool
848+ var wasUsed bool
849+ matcher , key , value := parseRuleSet (ruleSet .Value )
847850 for _ , selector := range selectors {
848- for _ , lm := range selector .LabelMatchers {
849- for _ , val := range ruleSetIgnoreLabelValValues (reporter , lm .Name , selector ) {
850- if ruleSet .Value == val {
851- used = true
852- goto NEXT
853- }
851+ if matcher != "" {
852+ isMatch , _ := matchSelectorToMetric (selector , matcher )
853+ if ! isMatch {
854+ continue
854855 }
855- for _ , val := range ruleSetMinAgePrefixes (reporter , selector ) {
856- if strings .HasPrefix (ruleSet .Value , val ) {
857- used = true
856+ }
857+ switch key {
858+ case "min-age" :
859+ wasUsed = true
860+ case "ignore/label-value" :
861+ for _ , lm := range selector .LabelMatchers {
862+ if lm .Name == value {
863+ wasUsed = true
858864 goto NEXT
859865 }
860866 }
861867 }
862868 }
863869 NEXT:
864- if ! used {
870+ if ! wasUsed {
865871 orhpaned = append (orhpaned , ruleSet )
866872 }
867873 }
0 commit comments