LCOV - code coverage report
Current view: top level - pebble - lsm_view.go (source / functions) Coverage Total Hit
Test: 2025-06-04 08:18Z 12c215c4 - tests only.lcov Lines: 66.7 % 198 132
Test Date: 2025-06-04 08:18:57 Functions: - 0 0

            Line data    Source code
       1              : // Copyright 2024 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 pebble
       6              : 
       7              : import (
       8              :         "context"
       9              :         "fmt"
      10              :         "slices"
      11              :         "strings"
      12              : 
      13              :         "github.com/cockroachdb/pebble/internal/base"
      14              :         "github.com/cockroachdb/pebble/internal/humanize"
      15              :         "github.com/cockroachdb/pebble/internal/lsmview"
      16              :         "github.com/cockroachdb/pebble/internal/manifest"
      17              :         "github.com/cockroachdb/pebble/objstorage"
      18              : )
      19              : 
      20              : // LSMViewURL returns an URL which shows a diagram of the LSM.
      21            1 : func (d *DB) LSMViewURL() string {
      22            1 :         v := func() *manifest.Version {
      23            1 :                 d.mu.Lock()
      24            1 :                 defer d.mu.Unlock()
      25            1 : 
      26            1 :                 v := d.mu.versions.currentVersion()
      27            1 :                 v.Ref()
      28            1 :                 return v
      29            1 :         }()
      30            1 :         defer v.Unref()
      31            1 : 
      32            1 :         b := lsmViewBuilder{
      33            1 :                 cmp:    d.opts.Comparer.Compare,
      34            1 :                 fmtKey: d.opts.Comparer.FormatKey,
      35            1 :         }
      36            1 :         if b.fmtKey == nil {
      37            0 :                 b.fmtKey = DefaultComparer.FormatKey
      38            0 :         }
      39            1 :         b.InitLevels(v)
      40            1 :         b.PopulateKeys()
      41            1 :         data := b.Build(d.objProvider, d.newIters)
      42            1 :         url, err := lsmview.GenerateURL(data)
      43            1 :         if err != nil {
      44            0 :                 return fmt.Sprintf("error: %s", err)
      45            0 :         }
      46            1 :         return url.String()
      47              : }
      48              : 
      49              : type lsmViewBuilder struct {
      50              :         cmp    base.Compare
      51              :         fmtKey base.FormatKey
      52              : 
      53              :         levelNames []string
      54              :         levels     [][]*manifest.TableMetadata
      55              : 
      56              :         // The keys that appear as Smallest/Largest, sorted and formatted.
      57              :         sortedKeys []string
      58              :         // keys[k] is the position of key k in the sortedKeys list.
      59              :         keys map[string]int
      60              : 
      61              :         // scanTables is set during Build. If we don't have too many tables, we will
      62              :         // create iterators and show some of the keys.
      63              :         scanTables bool
      64              : }
      65              : 
      66              : // InitLevels gets the metadata for the tables in the LSM and populates
      67              : // levelNames and levels.
      68            1 : func (b *lsmViewBuilder) InitLevels(v *manifest.Version) {
      69            1 :         var levelNames []string
      70            1 :         var levels [][]*manifest.TableMetadata
      71            1 :         for sublevel := len(v.L0SublevelFiles) - 1; sublevel >= 0; sublevel-- {
      72            1 :                 var files []*manifest.TableMetadata
      73            1 :                 for f := range v.L0SublevelFiles[sublevel].All() {
      74            1 :                         files = append(files, f)
      75            1 :                 }
      76              : 
      77            1 :                 levelNames = append(levelNames, fmt.Sprintf("L0.%d", sublevel))
      78            1 :                 levels = append(levels, files)
      79              :         }
      80            1 :         if len(levels) == 0 {
      81            0 :                 levelNames = append(levelNames, "L0")
      82            0 :                 levels = append(levels, nil)
      83            0 :         }
      84            1 :         for level := 1; level < len(v.Levels); level++ {
      85            1 :                 var files []*manifest.TableMetadata
      86            1 :                 for f := range v.Levels[level].All() {
      87            1 :                         files = append(files, f)
      88            1 :                 }
      89            1 :                 levelNames = append(levelNames, fmt.Sprintf("L%d", level))
      90            1 :                 levels = append(levels, files)
      91              :         }
      92            1 :         b.levelNames = levelNames
      93            1 :         b.levels = levels
      94              : }
      95              : 
      96              : // PopulateKeys initializes the sortedKeys and keys fields.
      97            1 : func (b *lsmViewBuilder) PopulateKeys() {
      98            1 :         // keys[k] will hold the position of k into sortedKeys.
      99            1 :         keys := make(map[string]int)
     100            1 :         for _, l := range b.levels {
     101            1 :                 for _, f := range l {
     102            1 :                         keys[string(f.Smallest().UserKey)] = -1
     103            1 :                         keys[string(f.Largest().UserKey)] = -1
     104            1 :                 }
     105              :         }
     106              : 
     107            1 :         sortedKeys := make([]string, 0, len(keys))
     108            1 :         for s := range keys {
     109            1 :                 sortedKeys = append(sortedKeys, s)
     110            1 :         }
     111            1 :         slices.SortFunc(sortedKeys, func(k1, k2 string) int {
     112            1 :                 return b.cmp([]byte(k1), []byte(k2))
     113            1 :         })
     114            1 :         sortedKeys = slices.CompactFunc(sortedKeys, func(k1, k2 string) bool {
     115            1 :                 return b.cmp([]byte(k1), []byte(k2)) == 0
     116            1 :         })
     117            1 :         for i, k := range sortedKeys {
     118            1 :                 keys[k] = i
     119            1 :         }
     120            1 :         for i := range sortedKeys {
     121            1 :                 sortedKeys[i] = fmt.Sprintf("%v", b.fmtKey([]byte(sortedKeys[i])))
     122            1 :         }
     123            1 :         b.sortedKeys = sortedKeys
     124            1 :         b.keys = keys
     125              : }
     126              : 
     127              : func (b *lsmViewBuilder) Build(
     128              :         objProvider objstorage.Provider, newIters tableNewIters,
     129            1 : ) lsmview.Data {
     130            1 :         n := 0
     131            1 :         for _, l := range b.levels {
     132            1 :                 n += len(l)
     133            1 :         }
     134            1 :         const scanTablesThreshold = 100
     135            1 :         b.scanTables = n <= scanTablesThreshold
     136            1 : 
     137            1 :         var data lsmview.Data
     138            1 :         data.Keys = b.sortedKeys
     139            1 :         data.Levels = make([]lsmview.Level, len(b.levels))
     140            1 :         for i, files := range b.levels {
     141            1 :                 l := &data.Levels[i]
     142            1 :                 l.Name = b.levelNames[i]
     143            1 :                 l.Tables = make([]lsmview.Table, len(files))
     144            1 :                 for j, f := range files {
     145            1 :                         t := &l.Tables[j]
     146            1 :                         if !f.Virtual {
     147            1 :                                 t.Label = fmt.Sprintf("%d", f.TableNum)
     148            1 :                         } else {
     149            0 :                                 t.Label = fmt.Sprintf("%d (%d)", f.TableNum, f.TableBacking.DiskFileNum)
     150            0 :                         }
     151              : 
     152            1 :                         t.Size = f.Size
     153            1 :                         t.SmallestKey = b.keys[string(f.Smallest().UserKey)]
     154            1 :                         t.LargestKey = b.keys[string(f.Largest().UserKey)]
     155            1 :                         t.Details = b.tableDetails(f, objProvider, newIters)
     156              :                 }
     157              :         }
     158            1 :         return data
     159              : }
     160              : 
     161              : func (b *lsmViewBuilder) tableDetails(
     162              :         m *manifest.TableMetadata, objProvider objstorage.Provider, newIters tableNewIters,
     163            1 : ) []string {
     164            1 :         res := make([]string, 0, 10)
     165            1 :         outf := func(format string, args ...any) {
     166            1 :                 res = append(res, fmt.Sprintf(format, args...))
     167            1 :         }
     168              : 
     169            1 :         outf("%s: %s - %s", m.TableNum, m.Smallest().Pretty(b.fmtKey), m.Largest().Pretty(b.fmtKey))
     170            1 :         outf("size: %s", humanize.Bytes.Uint64(m.Size))
     171            1 :         if m.Virtual {
     172            0 :                 meta, err := objProvider.Lookup(base.FileTypeTable, m.TableBacking.DiskFileNum)
     173            0 :                 var backingInfo string
     174            0 :                 switch {
     175            0 :                 case err != nil:
     176            0 :                         backingInfo = fmt.Sprintf(" (error looking up object: %v)", err)
     177            0 :                 case meta.IsShared():
     178            0 :                         backingInfo = "shared; "
     179            0 :                 case meta.IsExternal():
     180            0 :                         backingInfo = "external; "
     181              :                 }
     182            0 :                 outf("virtual; backed by %s (%ssize: %s)", m.TableBacking.DiskFileNum, backingInfo, humanize.Bytes.Uint64(m.TableBacking.Size))
     183              :         }
     184            1 :         outf("seqnums: %d - %d", m.SmallestSeqNum, m.LargestSeqNum)
     185            1 :         if m.SyntheticPrefixAndSuffix.HasPrefix() {
     186            0 :                 // Note: we are abusing the key formatter by passing just the prefix.
     187            0 :                 outf("synthetic prefix: %s", b.fmtKey(m.SyntheticPrefixAndSuffix.Prefix()))
     188            0 :         }
     189            1 :         if m.SyntheticPrefixAndSuffix.HasSuffix() {
     190            0 :                 // Note: we are abusing the key formatter by passing just the suffix.
     191            0 :                 outf("synthetic suffix: %s", b.fmtKey(m.SyntheticPrefixAndSuffix.Suffix()))
     192            0 :         }
     193            1 :         var iters iterSet
     194            1 :         if b.scanTables {
     195            1 :                 var err error
     196            1 :                 iters, err = newIters(context.Background(), m, nil /* opts */, internalIterOpts{}, iterPointKeys|iterRangeDeletions|iterRangeKeys)
     197            1 :                 if err != nil {
     198            0 :                         outf("error opening table: %v", err)
     199            1 :                 } else {
     200            1 :                         defer func() { _ = iters.CloseAll() }()
     201              :                 }
     202              :         }
     203            1 :         const maxPoints = 14
     204            1 :         const maxRangeDels = 10
     205            1 :         const maxRangeKeys = 10
     206            1 :         if m.HasPointKeys {
     207            1 :                 outf("points: %s - %s", m.PointKeyBounds.Smallest().Pretty(b.fmtKey), m.PointKeyBounds.Largest().Pretty(b.fmtKey))
     208            1 :                 if b.scanTables {
     209            1 :                         n := 0
     210            1 :                         if it := iters.point; it != nil {
     211            1 :                                 for kv := it.First(); kv != nil; kv = it.Next() {
     212            1 :                                         if n == maxPoints {
     213            0 :                                                 outf("  ...")
     214            0 :                                                 break
     215              :                                         }
     216            1 :                                         outf("  %s", kv.K.Pretty(b.fmtKey))
     217            1 :                                         n++
     218              :                                 }
     219            1 :                                 if err := it.Error(); err != nil {
     220            0 :                                         outf("  error scanning points: %v", err)
     221            0 :                                 }
     222              :                         }
     223            1 :                         if n == 0 {
     224            0 :                                 outf("  no points")
     225            0 :                         }
     226              : 
     227            1 :                         n = 0
     228            1 :                         if it := iters.rangeDeletion; it != nil {
     229            0 :                                 span, err := it.First()
     230            0 :                                 for ; span != nil; span, err = it.Next() {
     231            0 :                                         if n == maxRangeDels {
     232            0 :                                                 outf(" ...")
     233            0 :                                                 break
     234              :                                         }
     235            0 :                                         seqNums := make([]string, len(span.Keys))
     236            0 :                                         for i, k := range span.Keys {
     237            0 :                                                 seqNums[i] = fmt.Sprintf("#%d", k.SeqNum())
     238            0 :                                         }
     239            0 :                                         outf("  [%s - %s): %s", b.fmtKey(span.Start), b.fmtKey(span.End), strings.Join(seqNums, ","))
     240            0 :                                         n++
     241              :                                 }
     242            0 :                                 if err != nil {
     243            0 :                                         outf("error scanning range dels: %v", err)
     244            0 :                                 }
     245              :                         }
     246            1 :                         if n == 0 {
     247            1 :                                 outf("  no range dels")
     248            1 :                         }
     249              :                 }
     250              :         }
     251            1 :         if m.HasRangeKeys {
     252            0 :                 outf("range keys: %s - %s", m.RangeKeyBounds.Smallest().Pretty(b.fmtKey), m.RangeKeyBounds.Largest().Pretty(b.fmtKey))
     253            0 :                 n := 0
     254            0 :                 if it := iters.rangeKey; it != nil {
     255            0 :                         span, err := it.First()
     256            0 :                         for ; span != nil; span, err = it.Next() {
     257            0 :                                 if n == maxRangeKeys {
     258            0 :                                         outf(" ...")
     259            0 :                                         break
     260              :                                 }
     261            0 :                                 keys := make([]string, len(span.Keys))
     262            0 :                                 for i, k := range span.Keys {
     263            0 :                                         keys[i] = k.String()
     264            0 :                                 }
     265            0 :                                 outf("  [%s, %s): {%s}", b.fmtKey(span.Start), b.fmtKey(span.End), strings.Join(keys, " "))
     266            0 :                                 n++
     267              :                         }
     268            0 :                         if err != nil {
     269            0 :                                 outf("error scanning range keys: %v", err)
     270            0 :                         }
     271              :                 }
     272            0 :                 if n == 0 {
     273            0 :                         outf("  no range keys")
     274            0 :                 }
     275              :         }
     276              : 
     277            1 :         return res
     278              : }
        

Generated by: LCOV version 2.0-1