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 : }
|