LCOV - code coverage report
Current view: top level - pebble - lsm_view.go (source / functions) Hit Total Coverage
Test: 2024-03-16 08:17Z 0eccafb7 - tests only.lcov Lines: 133 199 66.8 %
Date: 2024-03-16 08:18:04 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     [][]*fileMetadata
      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 [][]*fileMetadata
      70           1 :         for sublevel := len(v.L0SublevelFiles) - 1; sublevel >= 0; sublevel-- {
      71           1 :                 var files []*fileMetadata
      72           1 :                 v.L0SublevelFiles[sublevel].Each(func(f *fileMetadata) {
      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 []*fileMetadata
      85           1 :                 v.Levels[level].Slice().Each(func(f *fileMetadata) {
      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 *fileMetadata, 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.SyntheticPrefix.IsSet() {
     185           0 :                 // Note: we are abusing the key formatter by passing just the prefix.
     186           0 :                 outf("synthetic prefix: %s", b.fmtKey(m.SyntheticPrefix))
     187           0 :         }
     188           1 :         if m.SyntheticSuffix.IsSet() {
     189           0 :                 // Note: we are abusing the key formatter by passing just the suffix.
     190           0 :                 outf("synthetic suffix: %s", b.fmtKey(m.SyntheticSuffix))
     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 k, _ := it.First(); k != nil; k, _ = it.Next() {
     211           1 :                                         if n == maxPoints {
     212           0 :                                                 outf("  ...")
     213           0 :                                                 break
     214             :                                         }
     215           1 :                                         outf("  %s", 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 1.14