Skip to content

Commit 82b6421

Browse files
committed
sstable: treesteps support for sstable iterators
1 parent 60241a4 commit 82b6421

File tree

10 files changed

+367
-26
lines changed

10 files changed

+367
-26
lines changed

sstable/blockiter/block_iter.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package blockiter
66

77
import (
88
"github.com/cockroachdb/pebble/internal/base"
9+
"github.com/cockroachdb/pebble/internal/treesteps"
910
"github.com/cockroachdb/pebble/sstable/block"
1011
)
1112

@@ -119,4 +120,6 @@ type Index interface {
119120
// the iterator must be reset such that it could be reused after a call to
120121
// Init or InitHandle.
121122
Close() error
123+
124+
treesteps.Node
122125
}

sstable/colblk/data_block.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1460,7 +1460,13 @@ func (i *DataBlockIter) String() string {
14601460

14611461
// TreeStepsNode is part of the InternalIterator interface.
14621462
func (i *DataBlockIter) TreeStepsNode() treesteps.NodeInfo {
1463-
return treesteps.NodeInfof(i, "%T(%p)", i, i)
1463+
ni := treesteps.NodeInfof(i, "colblk.DataBlockIter")
1464+
if i.Valid() {
1465+
ni.AddPropf("at", "%s", i.kv.K.String())
1466+
} else {
1467+
ni.AddPropf("not positioned", "")
1468+
}
1469+
return ni
14641470
}
14651471

14661472
// decodeRow decodes i.row into i.kv. If i.row is invalid, it returns nil.

sstable/colblk/index_block.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/cockroachdb/pebble/internal/binfmt"
1414
"github.com/cockroachdb/pebble/internal/invariants"
1515
"github.com/cockroachdb/pebble/internal/treeprinter"
16+
"github.com/cockroachdb/pebble/internal/treesteps"
1617
"github.com/cockroachdb/pebble/sstable/block"
1718
"github.com/cockroachdb/pebble/sstable/blockiter"
1819
)
@@ -389,3 +390,13 @@ func (i *IndexIter) Close() error {
389390
i.syntheticPrefixAndSuffix = blockiter.SyntheticPrefixAndSuffix{}
390391
return nil
391392
}
393+
394+
func (i *IndexIter) TreeStepsNode() treesteps.NodeInfo {
395+
ni := treesteps.NodeInfof(i, "colblk.IndexIter")
396+
if i.Valid() {
397+
ni.AddPropf("at", "%s", i.Separator())
398+
} else {
399+
ni.AddPropf("not positioned", "")
400+
}
401+
return ni
402+
}

sstable/reader_iter_single_lvl.go

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,9 @@ func (i *singleLevelIterator[I, PI, P, PD]) loadDataBlock(dir int8) loadBlockRes
516516
return loadBlockFailed
517517
}
518518
i.initBounds()
519+
if treesteps.Enabled && treesteps.IsRecording(i) {
520+
treesteps.NodeUpdated(i, fmt.Sprintf("loadDataBlock(%d) offset=%d length=%d", dir, i.dataBH.Offset, i.dataBH.Length))
521+
}
519522
return loadBlockOK
520523
}
521524

@@ -649,7 +652,13 @@ func (i *singleLevelIterator[I, PI, D, PD]) trySeekLTUsingPrevWithinBlock(
649652
// caller to ensure that key is greater than or equal to the lower bound.
650653
func (i *singleLevelIterator[I, PI, D, PD]) SeekGE(
651654
key []byte, flags base.SeekGEFlags,
652-
) *base.InternalKV {
655+
) (kv *base.InternalKV) {
656+
if treesteps.Enabled && treesteps.IsRecording(i) {
657+
op := treesteps.StartOpf(i, "SeekGE(%q, %d)", key, flags)
658+
defer func() {
659+
op.Finishf("= %s", kv.String())
660+
}()
661+
}
653662
// Clear the tracking flag since this is a new absolute positioning operation
654663
i.lastOpWasSeekPrefixGE.Set(false)
655664
// The synthetic key is no longer relevant and must be cleared.
@@ -697,7 +706,13 @@ func (i *singleLevelIterator[I, PI, D, PD]) SeekGE(
697706
// seekGEHelper contains the common functionality for SeekGE and SeekPrefixGE.
698707
func (i *singleLevelIterator[I, PI, D, PD]) seekGEHelper(
699708
key []byte, boundsCmp int, flags base.SeekGEFlags,
700-
) *base.InternalKV {
709+
) (kv *base.InternalKV) {
710+
if treesteps.Enabled && treesteps.IsRecording(i) {
711+
op := treesteps.StartOpf(i, "seekGEHelper(%q, %d, %d)", key, boundsCmp, flags)
712+
defer func() {
713+
op.Finishf("= %s", kv.String())
714+
}()
715+
}
701716
if !i.ensureIndexLoaded() {
702717
return nil
703718
}
@@ -808,7 +823,13 @@ func (i *singleLevelIterator[I, PI, D, PD]) seekGEHelper(
808823
// to the caller to ensure that key is greater than or equal to the lower bound.
809824
func (i *singleLevelIterator[I, PI, D, PD]) SeekPrefixGE(
810825
prefix, key []byte, flags base.SeekGEFlags,
811-
) *base.InternalKV {
826+
) (kv *base.InternalKV) {
827+
if treesteps.Enabled && treesteps.IsRecording(i) {
828+
op := treesteps.StartOpf(i, "SeekPrefixGE(%q, %q, %d)", prefix, key, flags)
829+
defer func() {
830+
op.Finishf("= %s", kv.String())
831+
}()
832+
}
812833
if i.synthetic.atSyntheticKey {
813834
// TODO(sachin) : We have to disable the optimization to avoid false data
814835
// invalidation if there are back to back SeekPrefixGE calls. Currently
@@ -921,12 +942,16 @@ func (i *singleLevelIterator[I, PI, D, PD]) seekPrefixGE(
921942
return nil
922943
}
923944
if !mayContain {
945+
if treesteps.Enabled && treesteps.IsRecording(i) {
946+
treesteps.UpdateLastOpf(i, "pass bloom filter did not match")
947+
}
924948
// In the no-error bloom filter miss case, the key is definitely not in table.
925949
// We can avoid invalidating the already loaded block since the caller is
926950
// not allowed to call Next when SeekPrefixGE returns nil.
927951
i.lastOpWasSeekPrefixGE.Set(true)
928952
return nil
929953
}
954+
treesteps.UpdateLastOpf(i, "bloom filter matched")
930955
i.lastBloomFilterMatched = true
931956
}
932957
if flags.TrySeekUsingNext() {
@@ -1093,7 +1118,13 @@ func (i *singleLevelIterator[I, PI, D, PD]) virtualLastSeekLE() *base.InternalKV
10931118
// caller to ensure that key is less than or equal to the upper bound.
10941119
func (i *singleLevelIterator[I, PI, D, PD]) SeekLT(
10951120
key []byte, flags base.SeekLTFlags,
1096-
) *base.InternalKV {
1121+
) (kv *base.InternalKV) {
1122+
if treesteps.Enabled && treesteps.IsRecording(i) {
1123+
op := treesteps.StartOpf(i, "SeekLT(%q, %d)", key, flags)
1124+
defer func() {
1125+
op.Finishf("= %s", kv.String())
1126+
}()
1127+
}
10971128
// Clear the tracking flag since this is a new absolute positioning operation
10981129
i.lastOpWasSeekPrefixGE.Set(false)
10991130
// The synthetic key is no longer relevant and must be cleared.
@@ -1208,7 +1239,13 @@ func (i *singleLevelIterator[I, PI, D, PD]) SeekLT(
12081239
// package. Note that First only checks the upper bound. It is up to the caller
12091240
// to ensure that key is greater than or equal to the lower bound (e.g. via a
12101241
// call to SeekGE(lower)).
1211-
func (i *singleLevelIterator[I, PI, D, PD]) First() *base.InternalKV {
1242+
func (i *singleLevelIterator[I, PI, D, PD]) First() (kv *base.InternalKV) {
1243+
if treesteps.Enabled && treesteps.IsRecording(i) {
1244+
op := treesteps.StartOpf(i, "First()")
1245+
defer func() {
1246+
op.Finishf("= %s", kv.String())
1247+
}()
1248+
}
12121249
// Clear the tracking flag since this is a new absolute positioning operation
12131250
i.lastOpWasSeekPrefixGE.Set(false)
12141251
// The synthetic key is no longer relevant and must be cleared.
@@ -1282,7 +1319,13 @@ func (i *singleLevelIterator[I, PI, D, PD]) firstInternal() *base.InternalKV {
12821319
// package. Note that Last only checks the lower bound. It is up to the caller
12831320
// to ensure that key is less than the upper bound (e.g. via a call to
12841321
// SeekLT(upper))
1285-
func (i *singleLevelIterator[I, PI, D, PD]) Last() *base.InternalKV {
1322+
func (i *singleLevelIterator[I, PI, D, PD]) Last() (kv *base.InternalKV) {
1323+
if treesteps.Enabled && treesteps.IsRecording(i) {
1324+
op := treesteps.StartOpf(i, "Last()")
1325+
defer func() {
1326+
op.Finishf("= %s", kv.String())
1327+
}()
1328+
}
12861329
// Clear the tracking flag since this is a new absolute positioning operation
12871330
i.lastOpWasSeekPrefixGE.Set(false)
12881331
// The synthetic key is no longer relevant and must be cleared.
@@ -1349,7 +1392,13 @@ func (i *singleLevelIterator[I, PI, D, PD]) lastInternal() *base.InternalKV {
13491392
// package.
13501393
// Note: compactionIterator.Next mirrors the implementation of Iterator.Next
13511394
// due to performance. Keep the two in sync.
1352-
func (i *singleLevelIterator[I, PI, D, PD]) Next() *base.InternalKV {
1395+
func (i *singleLevelIterator[I, PI, D, PD]) Next() (kv *base.InternalKV) {
1396+
if treesteps.Enabled && treesteps.IsRecording(i) {
1397+
op := treesteps.StartOpf(i, "Next()")
1398+
defer func() {
1399+
op.Finishf("= %s", kv.String())
1400+
}()
1401+
}
13531402
// The SeekPrefixGE might have returned a synthetic key with latest suffix
13541403
// contained in the sstable. If the caller is calling Next(), that means
13551404
// they want to move past the synthetic key and Next() is responsible for
@@ -1398,7 +1447,13 @@ func (i *singleLevelIterator[I, PI, D, PD]) Next() *base.InternalKV {
13981447
}
13991448

14001449
// NextPrefix implements (base.InternalIterator).NextPrefix.
1401-
func (i *singleLevelIterator[I, PI, D, PD]) NextPrefix(succKey []byte) *base.InternalKV {
1450+
func (i *singleLevelIterator[I, PI, D, PD]) NextPrefix(succKey []byte) (kv *base.InternalKV) {
1451+
if treesteps.Enabled && treesteps.IsRecording(i) {
1452+
op := treesteps.StartOpf(i, "NextPrefix(%q)", succKey)
1453+
defer func() {
1454+
op.Finishf("= %s", kv.String())
1455+
}()
1456+
}
14021457
// Clear the tracking flag since this is a relative positioning operation
14031458
i.lastOpWasSeekPrefixGE.Set(false)
14041459
if i.exhaustedBounds == +1 {
@@ -1477,7 +1532,13 @@ func (i *singleLevelIterator[I, PI, D, PD]) NextPrefix(succKey []byte) *base.Int
14771532

14781533
// Prev implements internalIterator.Prev, as documented in the pebble
14791534
// package.
1480-
func (i *singleLevelIterator[I, PI, D, PD]) Prev() *base.InternalKV {
1535+
func (i *singleLevelIterator[I, PI, D, PD]) Prev() (kv *base.InternalKV) {
1536+
if treesteps.Enabled && treesteps.IsRecording(i) {
1537+
op := treesteps.StartOpf(i, "Prev()")
1538+
defer func() {
1539+
op.Finishf("= %s", kv.String())
1540+
}()
1541+
}
14811542
// Clear the tracking flag since this is a relative positioning operation
14821543
i.lastOpWasSeekPrefixGE.Set(false)
14831544
if i.exhaustedBounds == -1 {
@@ -1710,9 +1771,20 @@ func (i *singleLevelIterator[I, PI, D, PD]) String() string {
17101771

17111772
// TreeStepsNode is part of the InternalIterator interface.
17121773
func (i *singleLevelIterator[I, PI, D, PD]) TreeStepsNode() treesteps.NodeInfo {
1713-
ni := treesteps.NodeInfof(i, "%T(%p)", i, i)
1714-
ni.AddPropf("fileNum", "%s", i)
1715-
return ni
1774+
info := treesteps.NodeInfof(i, "sstable.singleLevelIterator")
1775+
if PD(&i.data).Valid() {
1776+
info.AddPropf("at", "%s", PD(&i.data).KV().K.String())
1777+
} else {
1778+
info.AddPropf("not positioned", "")
1779+
}
1780+
if i.indexLoaded && !PI(&i.index).IsDataInvalidated() {
1781+
info.AddChildren(PI(&i.index))
1782+
}
1783+
if !PD(&i.data).IsDataInvalidated() {
1784+
info.AddChildren(PD(&i.data))
1785+
}
1786+
info.AddPropf("file", "%s", i.String())
1787+
return info
17161788
}
17171789

17181790
func (i *singleLevelIterator[I, PI, D, PD]) ensureIndexLoaded() bool {
@@ -1734,5 +1806,8 @@ func (i *singleLevelIterator[I, PI, D, PD]) ensureIndexLoaded() bool {
17341806
}
17351807

17361808
i.indexLoaded = true
1809+
if treesteps.Enabled && treesteps.IsRecording(i) {
1810+
treesteps.NodeUpdated(i, "index block loaded")
1811+
}
17371812
return true
17381813
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright 2025 The LevelDB-Go and Pebble Authors. All rights reserved. Use
2+
// of this source code is governed by a BSD-style license that can be found in
3+
// the LICENSE file.
4+
5+
package sstable
6+
7+
import (
8+
"testing"
9+
10+
"github.com/cockroachdb/datadriven"
11+
"github.com/cockroachdb/pebble/internal/testkeys"
12+
"github.com/cockroachdb/pebble/internal/treesteps"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
// TestTreeSteps tests the treesteps recording for singleLevelIterator and
17+
// twoLevelIterator operations, generating visualization URLs showing iterator
18+
// behavior.
19+
func TestIterTreeSteps(t *testing.T) {
20+
if !treesteps.Enabled {
21+
t.Skip("treesteps not available in this build")
22+
}
23+
24+
t.Run("single-level", func(t *testing.T) {
25+
testIterTreeSteps(t, "testdata/treesteps_single_level_iter")
26+
})
27+
28+
t.Run("two-level", func(t *testing.T) {
29+
testIterTreeSteps(t, "testdata/treesteps_two_level_iter")
30+
})
31+
}
32+
33+
func testIterTreeSteps(t *testing.T, testdataPath string) {
34+
var r *Reader
35+
defer func() {
36+
if r != nil {
37+
_ = r.Close()
38+
r = nil
39+
}
40+
}()
41+
datadriven.RunTest(t, testdataPath, func(t *testing.T, td *datadriven.TestData) string {
42+
switch td.Cmd {
43+
case "build":
44+
if r != nil {
45+
_ = r.Close()
46+
r = nil
47+
}
48+
opts := &WriterOptions{
49+
Comparer: testkeys.Comparer,
50+
TableFormat: TableFormatMax,
51+
}
52+
var err error
53+
_, r, err = runBuildCmd(td, opts, nil /* cacheHandle */)
54+
if err != nil {
55+
t.Fatalf("%s", err)
56+
}
57+
return ""
58+
59+
case "iter-treesteps":
60+
return runIterTreeStepsCmd(t, r, td)
61+
62+
default:
63+
return "unknown command"
64+
}
65+
})
66+
}
67+
68+
func runIterTreeStepsCmd(t *testing.T, r *Reader, td *datadriven.TestData) string {
69+
require.NotNil(t, r, "must run 'build' before 'iter-treesteps'")
70+
71+
iter, err := r.NewIter(NoTransforms, nil /* lower */, nil /* upper */, TableBlobContext{})
72+
require.NoError(t, err)
73+
74+
rec := treesteps.StartRecording(iter, td.Pos)
75+
runIterCmd(td, iter, false)
76+
77+
steps := rec.Finish()
78+
url := steps.URL()
79+
return url.String()
80+
}

0 commit comments

Comments
 (0)