go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/common/project_distribuition.go (about)

     1  // Copyright 2020 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 common
    16  
    17  import (
    18  	"hash/fnv"
    19  	"time"
    20  )
    21  
    22  // DistributeOffset deterministically chooses an offset across keys
    23  // in [1..pollInterval) range aimining for uniform distribution across all keys.
    24  //
    25  // A key is simply a concatenation of key parts with '\0' filler in between.
    26  func DistributeOffset(pollInterval time.Duration, keyParts ...string) time.Duration {
    27  	// Basic idea: interval/N*random(0..N), but deterministic on kind+luciProject.
    28  	// Use fast hash function, as we don't need strong collision resistance.
    29  	h := fnv.New32a()
    30  	for _, part := range keyParts {
    31  		h.Write([]byte(part))
    32  		h.Write([]byte{0})
    33  	}
    34  	r := h.Sum32()
    35  
    36  	i := int64(pollInterval)
    37  	// Avoid losing precision for low pollInterval values by first shifting them
    38  	// the more significant bits.
    39  	shifted := 0
    40  	for i < (int64(1) << 55) {
    41  		i = i << 7
    42  		shifted += 7
    43  	}
    44  	// i = i / N * r, where N = 2^32 since r is (0..2^32-1).
    45  	i = (i >> 32) * int64(r)
    46  	return time.Duration(i >> shifted)
    47  }