github.com/zuoyebang/bitalosdb@v1.1.1-0.20240516111551-79a8c4d8ce20/iterator.go (about)

     1  // Copyright 2021 The Bitalosdb author(hustxrb@163.com) and other contributors.
     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 bitalosdb
    16  
    17  import (
    18  	"bytes"
    19  	"unsafe"
    20  
    21  	"github.com/zuoyebang/bitalosdb/internal/consts"
    22  	"github.com/zuoyebang/bitalosdb/internal/invariants"
    23  	"github.com/zuoyebang/bitalosdb/internal/utils"
    24  
    25  	"github.com/cockroachdb/errors"
    26  	"github.com/cockroachdb/redact"
    27  )
    28  
    29  type iterPos int8
    30  
    31  const (
    32  	iterPosCurForward       iterPos = 0
    33  	iterPosNext             iterPos = 1
    34  	iterPosPrev             iterPos = -1
    35  	iterPosCurReverse       iterPos = -2
    36  	iterPosCurForwardPaused iterPos = 2
    37  	iterPosCurReversePaused iterPos = -3
    38  )
    39  
    40  const maxKeyBufCacheSize = 4 << 10
    41  
    42  const iterAllFlag = -1
    43  
    44  var errReversePrefixIteration = errors.New("bitalosdb: unsupported reverse prefix iteration")
    45  
    46  type IteratorMetrics struct {
    47  	ReadAmp int
    48  }
    49  
    50  type IteratorStatsKind int8
    51  
    52  const (
    53  	InterfaceCall IteratorStatsKind = iota
    54  	InternalIterCall
    55  	NumStatsKind
    56  )
    57  
    58  type IteratorStats struct {
    59  	ForwardSeekCount [NumStatsKind]int
    60  	ReverseSeekCount [NumStatsKind]int
    61  	ForwardStepCount [NumStatsKind]int
    62  	ReverseStepCount [NumStatsKind]int
    63  }
    64  
    65  var _ redact.SafeFormatter = &IteratorStats{}
    66  
    67  type Iterator struct {
    68  	opts                IterOptions
    69  	cmp                 Compare
    70  	equal               Equal
    71  	split               Split
    72  	iter                internalIterator
    73  	err                 error
    74  	key                 []byte
    75  	keyBuf              []byte
    76  	value               []byte
    77  	valueBuf            []byte
    78  	iterKey             *InternalKey
    79  	iterValue           []byte
    80  	alloc               *iterAlloc
    81  	prefixOrFullSeekKey []byte
    82  	stats               IteratorStats
    83  	seqNum              uint64
    84  	iterValidityState   IterValidityState
    85  	pos                 iterPos
    86  	hasPrefix           bool
    87  	lastPositioningOp   lastPositioningOpKind
    88  	index               int
    89  	bitower             *Bitower
    90  	readState           *readState
    91  	readStates          []*readState
    92  }
    93  
    94  type lastPositioningOpKind int8
    95  
    96  const (
    97  	unknownLastPositionOp lastPositioningOpKind = iota
    98  	seekPrefixGELastPositioningOp
    99  	seekGELastPositioningOp
   100  	seekLTLastPositioningOp
   101  )
   102  
   103  type IterValidityState int8
   104  
   105  const (
   106  	IterExhausted IterValidityState = iota
   107  	IterValid
   108  )
   109  
   110  func (i *Iterator) findNextEntry() {
   111  	i.iterValidityState = IterExhausted
   112  	i.pos = iterPosCurForward
   113  
   114  	for i.iterKey != nil {
   115  		key := *i.iterKey
   116  
   117  		if i.hasPrefix {
   118  			if n := i.split(key.UserKey); !bytes.Equal(i.prefixOrFullSeekKey, key.UserKey[:n]) {
   119  				return
   120  			}
   121  		}
   122  
   123  		switch key.Kind() {
   124  		case InternalKeyKindDelete, InternalKeyKindPrefixDelete:
   125  			i.nextUserKey()
   126  			continue
   127  		case InternalKeyKindSet:
   128  			i.keyBuf = append(i.keyBuf[:0], key.UserKey...)
   129  			i.key = i.keyBuf
   130  			i.value = i.iterValue
   131  			i.iterValidityState = IterValid
   132  			return
   133  		default:
   134  			i.err = errors.Errorf("bitalosdb: invalid internal key kind %d", key.Kind())
   135  			i.iterValidityState = IterExhausted
   136  			return
   137  		}
   138  	}
   139  }
   140  
   141  func (i *Iterator) nextUserKey() {
   142  	if i.iterKey == nil {
   143  		return
   144  	}
   145  	done := i.iterKey.SeqNum() == 0
   146  	if i.iterValidityState != IterValid {
   147  		i.keyBuf = append(i.keyBuf[:0], i.iterKey.UserKey...)
   148  		i.key = i.keyBuf
   149  	}
   150  	for {
   151  		i.iterKey, i.iterValue = i.iter.Next()
   152  		i.stats.ForwardStepCount[InternalIterCall]++
   153  		if done || i.iterKey == nil {
   154  			break
   155  		}
   156  		if !i.equal(i.key, i.iterKey.UserKey) {
   157  			break
   158  		}
   159  		done = i.iterKey.SeqNum() == 0
   160  	}
   161  }
   162  
   163  func (i *Iterator) findPrevEntry() {
   164  	i.iterValidityState = IterExhausted
   165  	i.pos = iterPosCurReverse
   166  
   167  	for i.iterKey != nil {
   168  		key := *i.iterKey
   169  
   170  		if i.iterValidityState == IterValid {
   171  			if !i.equal(key.UserKey, i.key) {
   172  				i.pos = iterPosPrev
   173  				if i.err != nil {
   174  					i.iterValidityState = IterExhausted
   175  				}
   176  				return
   177  			}
   178  		}
   179  
   180  		switch key.Kind() {
   181  		case InternalKeyKindDelete, InternalKeyKindPrefixDelete:
   182  			i.value = nil
   183  			i.iterValidityState = IterExhausted
   184  			i.iterKey, i.iterValue = i.iter.Prev()
   185  			i.stats.ReverseStepCount[InternalIterCall]++
   186  		case InternalKeyKindSet:
   187  			i.keyBuf = append(i.keyBuf[:0], key.UserKey...)
   188  			i.key = i.keyBuf
   189  			i.value = i.iterValue
   190  			i.iterValidityState = IterValid
   191  			i.iterKey, i.iterValue = i.iter.Prev()
   192  			i.stats.ReverseStepCount[InternalIterCall]++
   193  		default:
   194  			i.err = errors.Errorf("bitalosdb: invalid internal key kind %d", key.Kind())
   195  			i.iterValidityState = IterExhausted
   196  			return
   197  		}
   198  	}
   199  
   200  	if i.iterValidityState == IterValid {
   201  		i.pos = iterPosPrev
   202  		if i.err != nil {
   203  			i.iterValidityState = IterExhausted
   204  		}
   205  	}
   206  }
   207  
   208  func (i *Iterator) prevUserKey() {
   209  	if i.iterKey == nil {
   210  		return
   211  	}
   212  	if i.iterValidityState != IterValid {
   213  		i.keyBuf = append(i.keyBuf[:0], i.iterKey.UserKey...)
   214  		i.key = i.keyBuf
   215  	}
   216  	for {
   217  		i.iterKey, i.iterValue = i.iter.Prev()
   218  		i.stats.ReverseStepCount[InternalIterCall]++
   219  		if i.iterKey == nil {
   220  			break
   221  		}
   222  		if !i.equal(i.key, i.iterKey.UserKey) {
   223  			break
   224  		}
   225  	}
   226  }
   227  
   228  func (i *Iterator) SeekGE(key []byte) bool {
   229  	return i.SeekGEWithLimit(key) == IterValid
   230  }
   231  
   232  func (i *Iterator) SeekGEWithLimit(key []byte) IterValidityState {
   233  	lastPositioningOp := i.lastPositioningOp
   234  	i.lastPositioningOp = unknownLastPositionOp
   235  
   236  	i.err = nil
   237  	i.hasPrefix = false
   238  	i.stats.ForwardSeekCount[InterfaceCall]++
   239  	if lowerBound := i.opts.GetLowerBound(); lowerBound != nil && i.cmp(key, lowerBound) < 0 {
   240  		key = lowerBound
   241  	} else if upperBound := i.opts.GetUpperBound(); upperBound != nil && i.cmp(key, upperBound) > 0 {
   242  		key = upperBound
   243  	}
   244  	seekInternalIter := true
   245  	if lastPositioningOp == seekGELastPositioningOp {
   246  		cmp := i.cmp(i.prefixOrFullSeekKey, key)
   247  		if cmp <= 0 {
   248  			if i.iterValidityState == IterExhausted ||
   249  				(i.iterValidityState == IterValid && i.cmp(key, i.key) <= 0) {
   250  				if !invariants.Enabled || !disableSeekOpt(key, uintptr(unsafe.Pointer(i))) {
   251  					i.lastPositioningOp = seekGELastPositioningOp
   252  					return i.iterValidityState
   253  				}
   254  			}
   255  			if i.pos == iterPosCurForwardPaused && i.cmp(key, i.iterKey.UserKey) <= 0 {
   256  				seekInternalIter = false
   257  			}
   258  		}
   259  	}
   260  	if seekInternalIter {
   261  		i.iterKey, i.iterValue = i.iter.SeekGE(key)
   262  		i.stats.ForwardSeekCount[InternalIterCall]++
   263  	}
   264  	i.findNextEntry()
   265  	if i.Error() == nil {
   266  		i.prefixOrFullSeekKey = append(i.prefixOrFullSeekKey[:0], key...)
   267  		i.lastPositioningOp = seekGELastPositioningOp
   268  	}
   269  	return i.iterValidityState
   270  }
   271  
   272  func (i *Iterator) SeekPrefixGE(key []byte) bool {
   273  	lastPositioningOp := i.lastPositioningOp
   274  	i.lastPositioningOp = unknownLastPositionOp
   275  	i.err = nil
   276  	i.stats.ForwardSeekCount[InterfaceCall]++
   277  
   278  	prefixLen := i.split(key)
   279  	keyPrefix := key[:prefixLen]
   280  	trySeekUsingNext := false
   281  	if lastPositioningOp == seekPrefixGELastPositioningOp {
   282  		cmp := i.cmp(i.prefixOrFullSeekKey, keyPrefix)
   283  		trySeekUsingNext = cmp < 0
   284  		if invariants.Enabled && trySeekUsingNext && disableSeekOpt(key, uintptr(unsafe.Pointer(i))) {
   285  			trySeekUsingNext = false
   286  		}
   287  	}
   288  	if cap(i.prefixOrFullSeekKey) < prefixLen {
   289  		i.prefixOrFullSeekKey = make([]byte, prefixLen)
   290  	} else {
   291  		i.prefixOrFullSeekKey = i.prefixOrFullSeekKey[:prefixLen]
   292  	}
   293  	i.hasPrefix = true
   294  	copy(i.prefixOrFullSeekKey, keyPrefix)
   295  
   296  	if lowerBound := i.opts.GetLowerBound(); lowerBound != nil && i.cmp(key, lowerBound) < 0 {
   297  		if n := i.split(lowerBound); !bytes.Equal(i.prefixOrFullSeekKey, lowerBound[:n]) {
   298  			i.err = errors.New("bitalosdb: SeekPrefixGE supplied with key outside of lower bound")
   299  			return false
   300  		}
   301  		key = lowerBound
   302  	} else if upperBound := i.opts.GetUpperBound(); upperBound != nil && i.cmp(key, upperBound) > 0 {
   303  		if n := i.split(upperBound); !bytes.Equal(i.prefixOrFullSeekKey, upperBound[:n]) {
   304  			i.err = errors.New("bitalosdb: SeekPrefixGE supplied with key outside of upper bound")
   305  			return false
   306  		}
   307  		key = upperBound
   308  	}
   309  
   310  	i.iterKey, i.iterValue = i.iter.SeekPrefixGE(i.prefixOrFullSeekKey, key, trySeekUsingNext)
   311  	i.stats.ForwardSeekCount[InternalIterCall]++
   312  	i.findNextEntry()
   313  	if i.Error() == nil {
   314  		i.lastPositioningOp = seekPrefixGELastPositioningOp
   315  	}
   316  	return i.iterValidityState == IterValid
   317  }
   318  
   319  func disableSeekOpt(key []byte, ptr uintptr) bool {
   320  	simpleHash := (11400714819323198485 * uint64(ptr)) >> 63
   321  	return key != nil && key[0]&byte(1) == 0 && simpleHash == 0
   322  }
   323  
   324  func (i *Iterator) SeekLT(key []byte) bool {
   325  	return i.SeekLTWithLimit(key) == IterValid
   326  }
   327  
   328  func (i *Iterator) SeekLTWithLimit(key []byte) IterValidityState {
   329  	lastPositioningOp := i.lastPositioningOp
   330  	i.lastPositioningOp = unknownLastPositionOp
   331  	i.err = nil
   332  	i.hasPrefix = false
   333  	i.stats.ReverseSeekCount[InterfaceCall]++
   334  	if upperBound := i.opts.GetUpperBound(); upperBound != nil && i.cmp(key, upperBound) > 0 {
   335  		key = upperBound
   336  	} else if lowerBound := i.opts.GetLowerBound(); lowerBound != nil && i.cmp(key, lowerBound) < 0 {
   337  		key = lowerBound
   338  	}
   339  	seekInternalIter := true
   340  	if lastPositioningOp == seekLTLastPositioningOp {
   341  		cmp := i.cmp(key, i.prefixOrFullSeekKey)
   342  		if cmp <= 0 {
   343  			if i.iterValidityState == IterExhausted ||
   344  				(i.iterValidityState == IterValid && i.cmp(i.key, key) < 0) {
   345  				if !invariants.Enabled || !disableSeekOpt(key, uintptr(unsafe.Pointer(i))) {
   346  					i.lastPositioningOp = seekLTLastPositioningOp
   347  					return i.iterValidityState
   348  				}
   349  			}
   350  			if i.pos == iterPosCurReversePaused && i.cmp(i.iterKey.UserKey, key) < 0 {
   351  				seekInternalIter = false
   352  			}
   353  		}
   354  	}
   355  	if seekInternalIter {
   356  		i.iterKey, i.iterValue = i.iter.SeekLT(key)
   357  		i.stats.ReverseSeekCount[InternalIterCall]++
   358  	}
   359  	i.findPrevEntry()
   360  	if i.Error() == nil {
   361  		i.prefixOrFullSeekKey = append(i.prefixOrFullSeekKey[:0], key...)
   362  		i.lastPositioningOp = seekLTLastPositioningOp
   363  	}
   364  	return i.iterValidityState
   365  }
   366  
   367  func (i *Iterator) First() bool {
   368  	i.err = nil
   369  	i.hasPrefix = false
   370  	i.lastPositioningOp = unknownLastPositionOp
   371  	i.stats.ForwardSeekCount[InterfaceCall]++
   372  	if lowerBound := i.opts.GetLowerBound(); lowerBound != nil {
   373  		i.iterKey, i.iterValue = i.iter.SeekGE(lowerBound)
   374  		i.stats.ForwardSeekCount[InternalIterCall]++
   375  	} else {
   376  		i.iterKey, i.iterValue = i.iter.First()
   377  		i.stats.ForwardSeekCount[InternalIterCall]++
   378  	}
   379  	i.findNextEntry()
   380  	return i.iterValidityState == IterValid
   381  }
   382  
   383  func (i *Iterator) Last() bool {
   384  	i.err = nil
   385  	i.hasPrefix = false
   386  	i.lastPositioningOp = unknownLastPositionOp
   387  	i.stats.ReverseSeekCount[InterfaceCall]++
   388  	if upperBound := i.opts.GetUpperBound(); upperBound != nil {
   389  		i.iterKey, i.iterValue = i.iter.SeekLT(upperBound)
   390  		i.stats.ReverseSeekCount[InternalIterCall]++
   391  	} else {
   392  		i.iterKey, i.iterValue = i.iter.Last()
   393  		i.stats.ReverseSeekCount[InternalIterCall]++
   394  	}
   395  	i.findPrevEntry()
   396  	return i.iterValidityState == IterValid
   397  }
   398  
   399  func (i *Iterator) Next() bool {
   400  	return i.NextWithLimit() == IterValid
   401  }
   402  
   403  func (i *Iterator) NextWithLimit() IterValidityState {
   404  	i.stats.ForwardStepCount[InterfaceCall]++
   405  
   406  	if i.err != nil {
   407  		return i.iterValidityState
   408  	}
   409  	i.lastPositioningOp = unknownLastPositionOp
   410  	switch i.pos {
   411  	case iterPosCurForward:
   412  		i.nextUserKey()
   413  	case iterPosCurForwardPaused:
   414  	case iterPosCurReverse:
   415  		if i.iterKey != nil {
   416  			i.err = errors.New("bitalosdb: switching from reverse to forward but iter is not at prev")
   417  			i.iterValidityState = IterExhausted
   418  			return i.iterValidityState
   419  		}
   420  
   421  		if lowerBound := i.opts.GetLowerBound(); lowerBound != nil {
   422  			i.iterKey, i.iterValue = i.iter.SeekGE(lowerBound)
   423  			i.stats.ForwardSeekCount[InternalIterCall]++
   424  		} else {
   425  			i.iterKey, i.iterValue = i.iter.First()
   426  			i.stats.ForwardSeekCount[InternalIterCall]++
   427  		}
   428  	case iterPosCurReversePaused:
   429  		if i.iterKey == nil {
   430  			i.err = errors.New("bitalosdb: switching paused from reverse to forward but iter is exhausted")
   431  			i.iterValidityState = IterExhausted
   432  			return i.iterValidityState
   433  		}
   434  		i.nextUserKey()
   435  	case iterPosPrev:
   436  		i.iterValidityState = IterExhausted
   437  		if i.iterKey == nil {
   438  			if lowerBound := i.opts.GetLowerBound(); lowerBound != nil {
   439  				i.iterKey, i.iterValue = i.iter.SeekGE(lowerBound)
   440  				i.stats.ForwardSeekCount[InternalIterCall]++
   441  			} else {
   442  				i.iterKey, i.iterValue = i.iter.First()
   443  				i.stats.ForwardSeekCount[InternalIterCall]++
   444  			}
   445  		} else {
   446  			i.nextUserKey()
   447  		}
   448  		i.nextUserKey()
   449  	case iterPosNext:
   450  	}
   451  	i.findNextEntry()
   452  	return i.iterValidityState
   453  }
   454  
   455  func (i *Iterator) Prev() bool {
   456  	return i.PrevWithLimit() == IterValid
   457  }
   458  
   459  func (i *Iterator) PrevWithLimit() IterValidityState {
   460  	i.stats.ReverseStepCount[InterfaceCall]++
   461  	if i.err != nil {
   462  		return i.iterValidityState
   463  	}
   464  	i.lastPositioningOp = unknownLastPositionOp
   465  	if i.hasPrefix {
   466  		i.err = errReversePrefixIteration
   467  		i.iterValidityState = IterExhausted
   468  		return i.iterValidityState
   469  	}
   470  	switch i.pos {
   471  	case iterPosCurForward:
   472  	case iterPosCurForwardPaused:
   473  	case iterPosCurReverse:
   474  		i.prevUserKey()
   475  	case iterPosCurReversePaused:
   476  	case iterPosNext:
   477  	case iterPosPrev:
   478  	}
   479  	if i.pos == iterPosCurForward || i.pos == iterPosNext || i.pos == iterPosCurForwardPaused {
   480  		stepAgain := i.pos == iterPosNext
   481  		i.iterValidityState = IterExhausted
   482  		if i.iterKey == nil {
   483  			if upperBound := i.opts.GetUpperBound(); upperBound != nil {
   484  				i.iterKey, i.iterValue = i.iter.SeekLT(upperBound)
   485  				i.stats.ReverseSeekCount[InternalIterCall]++
   486  			} else {
   487  				i.iterKey, i.iterValue = i.iter.Last()
   488  				i.stats.ReverseSeekCount[InternalIterCall]++
   489  			}
   490  		} else {
   491  			i.prevUserKey()
   492  		}
   493  		if stepAgain {
   494  			i.prevUserKey()
   495  		}
   496  	}
   497  	i.findPrevEntry()
   498  	return i.iterValidityState
   499  }
   500  
   501  func (i *Iterator) Key() []byte {
   502  	return i.key
   503  }
   504  
   505  func (i *Iterator) Value() []byte {
   506  	return i.value
   507  }
   508  
   509  func (i *Iterator) Valid() bool {
   510  	return i.iterValidityState == IterValid
   511  }
   512  
   513  func (i *Iterator) Error() error {
   514  	err := i.err
   515  	if i.iter != nil {
   516  		err = utils.FirstError(i.err, i.iter.Error())
   517  	}
   518  	return err
   519  }
   520  
   521  func (i *Iterator) Close() error {
   522  	if i.bitower != nil && i.getInternalStepCount() > consts.IterSlowCountThreshold {
   523  		i.bitower.iterSlowCount.Add(1)
   524  	}
   525  
   526  	if i.iter != nil {
   527  		i.err = utils.FirstError(i.err, i.iter.Close())
   528  	}
   529  	err := i.err
   530  
   531  	if i.readState != nil {
   532  		i.readState.unref()
   533  		i.readState = nil
   534  	} else if i.readStates != nil {
   535  		for j := range i.readStates {
   536  			i.readStates[j].unref()
   537  			i.readStates[j] = nil
   538  		}
   539  		i.readStates = nil
   540  	}
   541  
   542  	if alloc := i.alloc; alloc != nil {
   543  		if cap(i.keyBuf) >= maxKeyBufCacheSize {
   544  			alloc.keyBuf = nil
   545  		} else {
   546  			alloc.keyBuf = i.keyBuf
   547  		}
   548  		if cap(i.prefixOrFullSeekKey) >= maxKeyBufCacheSize {
   549  			alloc.prefixOrFullSeekKey = nil
   550  		} else {
   551  			alloc.prefixOrFullSeekKey = i.prefixOrFullSeekKey
   552  		}
   553  		*i = Iterator{}
   554  		iterAllocPool.Put(alloc)
   555  	}
   556  	return err
   557  }
   558  
   559  func (i *Iterator) SetBounds(lower, upper []byte) {
   560  	i.lastPositioningOp = unknownLastPositionOp
   561  	i.hasPrefix = false
   562  	i.iterKey = nil
   563  	i.iterValue = nil
   564  
   565  	switch i.pos {
   566  	case iterPosCurForward, iterPosNext, iterPosCurForwardPaused:
   567  		i.pos = iterPosCurForward
   568  	case iterPosCurReverse, iterPosPrev, iterPosCurReversePaused:
   569  		i.pos = iterPosCurReverse
   570  	}
   571  	i.iterValidityState = IterExhausted
   572  
   573  	i.opts.LowerBound = lower
   574  	i.opts.UpperBound = upper
   575  	i.iter.SetBounds(lower, upper)
   576  }
   577  
   578  func (i *Iterator) Metrics() IteratorMetrics {
   579  	m := IteratorMetrics{
   580  		ReadAmp: 1,
   581  	}
   582  	if mi, ok := i.iter.(*mergingIter); ok {
   583  		m.ReadAmp = len(mi.levels)
   584  	}
   585  	return m
   586  }
   587  
   588  func (i *Iterator) ResetStats() {
   589  	i.stats = IteratorStats{}
   590  }
   591  
   592  func (i *Iterator) Stats() IteratorStats {
   593  	return i.stats
   594  }
   595  
   596  func (i *Iterator) getInternalStepCount() int {
   597  	return i.stats.ForwardStepCount[1] + i.stats.ReverseStepCount[1]
   598  }
   599  
   600  func (i *Iterator) getInterfaceStepCount() int {
   601  	return i.stats.ForwardStepCount[0] + i.stats.ReverseStepCount[0]
   602  }
   603  
   604  func (i *Iterator) getInternalSeekCount() int {
   605  	return i.stats.ForwardSeekCount[1] + i.stats.ForwardSeekCount[1]
   606  }
   607  
   608  func (i *Iterator) getInterfaceSeekCount() int {
   609  	return i.stats.ForwardSeekCount[0] + i.stats.ForwardSeekCount[0]
   610  }
   611  
   612  func (stats *IteratorStats) String() string {
   613  	return redact.StringWithoutMarkers(stats)
   614  }
   615  
   616  func (stats *IteratorStats) SafeFormat(s redact.SafePrinter, verb rune) {
   617  	for i := range stats.ForwardStepCount {
   618  		switch IteratorStatsKind(i) {
   619  		case InterfaceCall:
   620  			s.SafeString("(interface (dir, seek, step): ")
   621  		case InternalIterCall:
   622  			s.SafeString(", (internal (dir, seek, step): ")
   623  		}
   624  		s.Printf("(fwd, %d, %d), (rev, %d, %d))",
   625  			stats.ForwardSeekCount[i], stats.ForwardStepCount[i],
   626  			stats.ReverseSeekCount[i], stats.ReverseStepCount[i])
   627  	}
   628  }