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

Generated by: LCOV version 2.0-1