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