github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/search/progress.go (about)

     1  // Copyright 2020 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package search
     6  
     7  import (
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/keybase/client/go/kbfs/libkbfs"
    12  	"github.com/keybase/client/go/kbfs/tlf"
    13  	"github.com/keybase/client/go/protocol/keybase1"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  type indexType int
    18  
    19  const (
    20  	indexNone indexType = iota
    21  	indexFull
    22  	indexIncremental
    23  )
    24  
    25  // Progress represents the current state of the indexer, and how far
    26  // along the indexing is.
    27  type Progress struct {
    28  	clock libkbfs.Clock
    29  
    30  	lock             sync.RWMutex
    31  	tlfSizesToIndex  map[tlf.ID]uint64
    32  	currTlf          tlf.ID
    33  	currTotalToIndex uint64
    34  	currHasIndexed   uint64
    35  	currIndexType    indexType
    36  	currStartTime    time.Time
    37  	lastIndexRate    float64 // (bytes/second)
    38  }
    39  
    40  // NewProgress creates a new Progress instance.
    41  func NewProgress(clock libkbfs.Clock) *Progress {
    42  	return &Progress{
    43  		clock:           clock,
    44  		tlfSizesToIndex: make(map[tlf.ID]uint64),
    45  	}
    46  }
    47  
    48  func (p *Progress) tlfQueue(id tlf.ID, sizeEstimate uint64) {
    49  	p.lock.Lock()
    50  	defer p.lock.Unlock()
    51  
    52  	// Overwrite whatever was already there.
    53  	p.tlfSizesToIndex[id] = sizeEstimate
    54  }
    55  
    56  func (p *Progress) tlfUnqueue(id tlf.ID) {
    57  	p.lock.Lock()
    58  	defer p.lock.Unlock()
    59  
    60  	delete(p.tlfSizesToIndex, id)
    61  }
    62  
    63  func (p *Progress) startIndex(
    64  	id tlf.ID, sizeEstimate uint64, t indexType) error {
    65  	p.lock.Lock()
    66  	defer p.lock.Unlock()
    67  
    68  	if p.currTlf != tlf.NullID {
    69  		return errors.Errorf("Cannot index %s before finishing index of %s",
    70  			id, p.currTlf)
    71  	}
    72  
    73  	p.currTlf = id
    74  	delete(p.tlfSizesToIndex, id)
    75  	p.currTotalToIndex = sizeEstimate
    76  	p.currHasIndexed = 0
    77  	p.currIndexType = t
    78  	p.currStartTime = p.clock.Now()
    79  	return nil
    80  }
    81  
    82  func (p *Progress) indexedBytes(size uint64) {
    83  	p.lock.Lock()
    84  	defer p.lock.Unlock()
    85  
    86  	p.currHasIndexed += size
    87  	if p.currHasIndexed > p.currTotalToIndex {
    88  		// The provided size estimate was wrong.  But we don't know by
    89  		// how much.  So just add the newly-indexed bytes onto it, ot
    90  		// make sure we're still under the limit.
    91  		p.currTotalToIndex += size
    92  	}
    93  }
    94  
    95  func (p *Progress) finishIndex(id tlf.ID) error {
    96  	p.lock.Lock()
    97  	defer p.lock.Unlock()
    98  
    99  	if id != p.currTlf {
   100  		return errors.Errorf(
   101  			"Cannot finish index for %s, because %s is the current TLF",
   102  			id, p.currTlf)
   103  	}
   104  
   105  	// Estimate how long these bytes took to index.
   106  	timeSecs := p.clock.Now().Sub(p.currStartTime).Seconds()
   107  	if timeSecs > 0 {
   108  		p.lastIndexRate = float64(p.currHasIndexed) / timeSecs
   109  	} else {
   110  		p.lastIndexRate = 0
   111  	}
   112  
   113  	p.currTlf = tlf.NullID
   114  	p.currTotalToIndex = 0
   115  	p.currHasIndexed = 0
   116  	p.currIndexType = indexNone
   117  	p.currStartTime = time.Time{}
   118  	return nil
   119  }
   120  
   121  func (p *Progress) fillInProgressRecord(
   122  	total, soFar uint64, rate float64, rec *keybase1.IndexProgressRecord) {
   123  	if rate > 0 {
   124  		bytesLeft := total - soFar
   125  		timeLeft := time.Duration(
   126  			(float64(bytesLeft) / rate) * float64(time.Second))
   127  		rec.EndEstimate = keybase1.ToTime(p.clock.Now().Add(timeLeft))
   128  	}
   129  	rec.BytesSoFar = int64(soFar)
   130  	rec.BytesTotal = int64(total)
   131  }
   132  
   133  // GetStatus returns the current progress status.
   134  func (p *Progress) GetStatus() (
   135  	currProgress, overallProgress keybase1.IndexProgressRecord,
   136  	currTlf tlf.ID, queuedTlfs []tlf.ID) {
   137  	p.lock.RLock()
   138  	defer p.lock.RUnlock()
   139  
   140  	// At what rate is the current indexer running?
   141  	rate := p.lastIndexRate
   142  	if !p.currStartTime.IsZero() && p.currHasIndexed != 0 {
   143  		timeSecs := p.clock.Now().Sub(p.currStartTime).Seconds()
   144  		rate = float64(p.currHasIndexed) / timeSecs
   145  	}
   146  
   147  	if p.currTlf != tlf.NullID {
   148  		p.fillInProgressRecord(
   149  			p.currTotalToIndex, p.currHasIndexed, rate, &currProgress)
   150  	}
   151  
   152  	queuedTlfs = make([]tlf.ID, 0, len(p.tlfSizesToIndex))
   153  	var totalSize, soFar uint64
   154  	if p.currTlf != tlf.NullID {
   155  		totalSize += p.currTotalToIndex
   156  		soFar += p.currHasIndexed
   157  	}
   158  	for id, size := range p.tlfSizesToIndex {
   159  		if id == p.currTlf {
   160  			continue
   161  		}
   162  		totalSize += size
   163  		queuedTlfs = append(queuedTlfs, id)
   164  	}
   165  	if totalSize != 0 {
   166  		p.fillInProgressRecord(
   167  			totalSize, soFar, rate, &overallProgress)
   168  	}
   169  
   170  	return currProgress, overallProgress, p.currTlf, queuedTlfs
   171  }