go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/internal/clustering/algorithms/rulesalgorithm/rules.go (about)

     1  // Copyright 2022 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package rulesalgorithm
    16  
    17  import (
    18  	"fmt"
    19  	"time"
    20  
    21  	"go.chromium.org/luci/analysis/internal/clustering"
    22  	"go.chromium.org/luci/analysis/internal/clustering/rules/cache"
    23  )
    24  
    25  type Algorithm struct{}
    26  
    27  // AlgorithmVersion is the version of the clustering algorithm. The algorithm
    28  // version should be incremented whenever existing test results may be
    29  // clustered differently (i.e. Cluster(f) returns a different value for some
    30  // f that may have been already ingested).
    31  const AlgorithmVersion = 3
    32  
    33  // AlgorithmName is the identifier for the clustering algorithm.
    34  // LUCI Analysis requires all clustering algorithms to have a unique
    35  // identifier. Must match the pattern ^[a-z0-9-.]{1,32}$.
    36  //
    37  // The AlgorithmName must encode the algorithm version, so that each version
    38  // of an algorithm has a different name.
    39  var AlgorithmName = fmt.Sprintf("%sv%v", clustering.RulesAlgorithmPrefix, AlgorithmVersion)
    40  
    41  // Cluster incrementally (re-)clusters the given test failure, updating the
    42  // matched cluster IDs. The passed existingRulesVersion and ruleIDs
    43  // should be the ruleset.RulesVersion and cluster IDs of the previous call
    44  // to Cluster (if any) from which incremental clustering should occur.
    45  //
    46  // If clustering has not been performed previously, and clustering is to be
    47  // performed from scratch, existingRulesVersion should be rules.StartingEpoch
    48  // and ruleIDs should be an empty list.
    49  //
    50  // This method is on the performance-critical path for re-clustering.
    51  //
    52  // To avoid unnecessary allocations, the method will modify the passed ruleIDs.
    53  func (a *Algorithm) Cluster(ruleset *cache.Ruleset, existingRulesVersion time.Time, ruleIDs map[string]struct{}, failure *clustering.Failure) {
    54  	for id := range ruleIDs {
    55  		// Remove matches with rules that are no longer active.
    56  		if !ruleset.IsRuleActive(id) {
    57  			delete(ruleIDs, id)
    58  		}
    59  	}
    60  
    61  	// For efficiency, only match new/modified rules since the
    62  	// last call to Cluster(...).
    63  	newRules := ruleset.ActiveRulesWithPredicateUpdatedSince(existingRulesVersion)
    64  	for _, r := range newRules {
    65  		if r.Expr.Evaluate(failure) {
    66  			ruleIDs[r.Rule.RuleID] = struct{}{}
    67  		} else {
    68  			// If this is a modified rule (rather than a new rule)
    69  			// it may have matched previously. Delete any existing
    70  			// match.
    71  			delete(ruleIDs, r.Rule.RuleID)
    72  		}
    73  	}
    74  }