go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/prjmanager/state/partition.go (about)

     1  // Copyright 2021 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 state
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"go.chromium.org/luci/common/data/disjointset"
    21  	"go.chromium.org/luci/cv/internal/common"
    22  	"go.chromium.org/luci/cv/internal/prjmanager/prjpb"
    23  )
    24  
    25  // repartition updates components.
    26  //
    27  // On success, guarantees the following:
    28  //   - .Pcls contains all referenced by components CLs.
    29  //     However, some deps of those CLs may be missing.
    30  //   - .Pcls doesn't contain any CLs not in components.
    31  //   - .CreatedRuns is nil,
    32  //   - .RepartitionRequired is false.
    33  //   - pclIndex is up-to-date.
    34  func (s *State) repartition(cat *categorizedCLs) {
    35  	s.ensurePCLIndex()
    36  	plan := s.planPartition(cat)
    37  	s.PB.Components = s.execPartition(cat, plan)
    38  
    39  	// Remove unused PCLs while updating pclIndex. This can be done only after
    40  	// components are updated to ensure components don't reference any unused CLs.
    41  	pcls := make([]*prjpb.PCL, 0, len(s.PB.GetPcls())-len(cat.unused))
    42  	for _, pcl := range s.PB.GetPcls() {
    43  		id := common.CLID(pcl.GetClid())
    44  		if cat.unused.Has(id) {
    45  			delete(s.pclIndex, id)
    46  		} else {
    47  			s.pclIndex[id] = len(pcls)
    48  			pcls = append(pcls, pcl)
    49  		}
    50  	}
    51  	s.PB.Pcls = pcls
    52  	s.PB.CreatedPruns = nil
    53  	s.PB.RepartitionRequired = false
    54  }
    55  
    56  // planPartition returns a DisjointSet representing a partition of PCLs.
    57  func (s *State) planPartition(cat *categorizedCLs) disjointset.DisjointSet {
    58  	s.ensurePCLIndex()
    59  	d := disjointset.New(len(s.PB.GetPcls())) // operates on indexes of PCLs
    60  
    61  	// CLs of a Run must be in the same set.
    62  	s.PB.IterIncompleteRuns(func(r *prjpb.PRun, _ *prjpb.Component) (stop bool) {
    63  		clids := r.GetClids()
    64  		first := s.pclIndex[common.CLID(clids[0])]
    65  		for _, id := range clids[1:] {
    66  			d.Merge(first, s.pclIndex[common.CLID(id)])
    67  		}
    68  		return
    69  	})
    70  	// Active deps of an active CL must be in the same set as the CL.
    71  	for i, pcl := range s.PB.GetPcls() {
    72  		if cat.active.HasI64(pcl.GetClid()) {
    73  			for _, dep := range pcl.GetDeps() {
    74  				id := dep.GetClid()
    75  				if j, exists := s.pclIndex[common.CLID(id)]; exists && cat.active.HasI64(id) {
    76  					d.Merge(i, j)
    77  				}
    78  			}
    79  		}
    80  	}
    81  	return d
    82  }
    83  
    84  // execPartition returns components according to partition plan.
    85  //
    86  // Expects pclIndex to be same used by planPartition.
    87  func (s *State) execPartition(cat *categorizedCLs, d disjointset.DisjointSet) []*prjpb.Component {
    88  
    89  	canReuse := func(c *prjpb.Component) (root int, can bool) {
    90  		// Old component can be re-used iff all of:
    91  		//  (1) it has exactly the same set in the new partition.
    92  		//  (2) it does not contain unused CLs.
    93  		//  (3) it contains at least 1 active CL.
    94  		clids := c.GetClids()
    95  
    96  		// Check (1).
    97  		root = d.RootOf(s.pclIndex[common.CLID(clids[0])])
    98  		if d.SizeOf(root) != len(clids) {
    99  			return -1, false
   100  		}
   101  		for _, id := range clids[1:] {
   102  			if root != d.RootOf(s.pclIndex[common.CLID(id)]) {
   103  				return -1, false
   104  			}
   105  		}
   106  
   107  		// Check (2) and (3).
   108  		hasActive := false
   109  		for _, clid := range clids {
   110  			if cat.unused.HasI64(clid) {
   111  				if len(clids) != 1 {
   112  					// Note that 2+ CL component which satisfies (1) can't have an unused
   113  					// CL. Unused CL don't have relation to other CLs, and so wouldn't be
   114  					// grouped into the same set in a new partition.
   115  					panic(fmt.Errorf("component with %d CLs %v has unused CL %d", len(clids), clids, clid))
   116  				}
   117  				return -1, false
   118  			}
   119  			if cat.active.HasI64(clid) {
   120  				hasActive = true
   121  			}
   122  		}
   123  		if !hasActive {
   124  			return -1, false
   125  		}
   126  
   127  		return root, true
   128  	}
   129  	// First, try to re-use existing components whenever possible.
   130  	// Typically, this should cover most components.
   131  	reused := make(map[int]*prjpb.Component, d.Count())
   132  	var deleted []*prjpb.Component
   133  	for _, c := range s.PB.GetComponents() {
   134  		if root, yes := canReuse(c); yes {
   135  			reused[root] = c
   136  			continue
   137  		}
   138  		deleted = append(deleted, c)
   139  	}
   140  
   141  	// Now create new components.
   142  	created := make(map[int]*prjpb.Component, d.Count()-len(reused))
   143  	for index, pcl := range s.PB.GetPcls() {
   144  		id := pcl.GetClid()
   145  		if !cat.active.HasI64(id) {
   146  			if size := d.SizeOf(index); size != 1 {
   147  				panic(fmt.Errorf("inactive CLs must end up in a disjoint set of their own: %d => %d", id, size))
   148  			}
   149  			continue // no component for inactive CLs.
   150  		}
   151  		root := d.RootOf(index)
   152  		if _, ok := reused[root]; ok {
   153  			continue
   154  		}
   155  		c, exists := created[root]
   156  		if !exists {
   157  			size := d.SizeOf(root)
   158  			c = &prjpb.Component{
   159  				Clids:          make([]int64, 0, size),
   160  				TriageRequired: true,
   161  			}
   162  			created[root] = c
   163  		}
   164  		c.Clids = append(c.GetClids(), id)
   165  	}
   166  
   167  	// And fill them with Incomplete runs.
   168  	addPRuns := func(pruns []*prjpb.PRun) {
   169  		for _, r := range pruns {
   170  			index := s.pclIndex[common.CLID(r.GetClids()[0])]
   171  			c := created[d.RootOf(index)]
   172  			c.Pruns = append(c.GetPruns(), r) // not yet sorted order.
   173  		}
   174  	}
   175  	addPRuns(s.PB.GetCreatedPruns())
   176  	for _, c := range deleted {
   177  		addPRuns(c.GetPruns())
   178  	}
   179  
   180  	out := make([]*prjpb.Component, 0, len(reused)+len(created))
   181  	for _, c := range reused {
   182  		out = append(out, c)
   183  	}
   184  	for _, c := range created {
   185  		prjpb.SortPRuns(c.Pruns)
   186  		out = append(out, c)
   187  	}
   188  	return out
   189  }