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 0 : func (d *DB) LSMViewURL() string {
21 0 : v := func() *version {
22 0 : d.mu.Lock()
23 0 : defer d.mu.Unlock()
24 0 :
25 0 : v := d.mu.versions.currentVersion()
26 0 : v.Ref()
27 0 : return v
28 0 : }()
29 0 : defer v.Unref()
30 0 :
31 0 : b := lsmViewBuilder{
32 0 : cmp: d.opts.Comparer.Compare,
33 0 : fmtKey: d.opts.Comparer.FormatKey,
34 0 : }
35 0 : if b.fmtKey == nil {
36 0 : b.fmtKey = DefaultComparer.FormatKey
37 0 : }
38 0 : b.InitLevels(v)
39 0 : b.PopulateKeys()
40 0 : data := b.Build(d.objProvider, d.newIters)
41 0 : url, err := lsmview.GenerateURL(data)
42 0 : if err != nil {
43 0 : return fmt.Sprintf("error: %s", err)
44 0 : }
45 0 : 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 0 : func (b *lsmViewBuilder) InitLevels(v *version) {
68 0 : var levelNames []string
69 0 : var levels [][]*fileMetadata
70 0 : for sublevel := len(v.L0SublevelFiles) - 1; sublevel >= 0; sublevel-- {
71 0 : var files []*fileMetadata
72 0 : v.L0SublevelFiles[sublevel].Each(func(f *fileMetadata) {
73 0 : files = append(files, f)
74 0 : })
75 :
76 0 : levelNames = append(levelNames, fmt.Sprintf("L0.%d", sublevel))
77 0 : levels = append(levels, files)
78 : }
79 0 : if len(levels) == 0 {
80 0 : levelNames = append(levelNames, "L0")
81 0 : levels = append(levels, nil)
82 0 : }
83 0 : for level := 1; level < len(v.Levels); level++ {
84 0 : var files []*fileMetadata
85 0 : v.Levels[level].Slice().Each(func(f *fileMetadata) {
86 0 : files = append(files, f)
87 0 : })
88 0 : levelNames = append(levelNames, fmt.Sprintf("L%d", level))
89 0 : levels = append(levels, files)
90 : }
91 0 : b.levelNames = levelNames
92 0 : b.levels = levels
93 : }
94 :
95 : // PopulateKeys initializes the sortedKeys and keys fields.
96 0 : func (b *lsmViewBuilder) PopulateKeys() {
97 0 : // keys[k] will hold the position of k into sortedKeys.
98 0 : keys := make(map[string]int)
99 0 : for _, l := range b.levels {
100 0 : for _, f := range l {
101 0 : keys[string(f.Smallest.UserKey)] = -1
102 0 : keys[string(f.Largest.UserKey)] = -1
103 0 : }
104 : }
105 :
106 0 : sortedKeys := make([]string, 0, len(keys))
107 0 : for s := range keys {
108 0 : sortedKeys = append(sortedKeys, s)
109 0 : }
110 0 : slices.SortFunc(sortedKeys, func(k1, k2 string) int {
111 0 : return b.cmp([]byte(k1), []byte(k2))
112 0 : })
113 0 : sortedKeys = slices.CompactFunc(sortedKeys, func(k1, k2 string) bool {
114 0 : return b.cmp([]byte(k1), []byte(k2)) == 0
115 0 : })
116 0 : for i, k := range sortedKeys {
117 0 : keys[k] = i
118 0 : }
119 0 : for i := range sortedKeys {
120 0 : sortedKeys[i] = fmt.Sprintf("%v", b.fmtKey([]byte(sortedKeys[i])))
121 0 : }
122 0 : b.sortedKeys = sortedKeys
123 0 : b.keys = keys
124 : }
125 :
126 : func (b *lsmViewBuilder) Build(
127 : objProvider objstorage.Provider, newIters tableNewIters,
128 0 : ) lsmview.Data {
129 0 : n := 0
130 0 : for _, l := range b.levels {
131 0 : n += len(l)
132 0 : }
133 0 : const scanTablesThreshold = 100
134 0 : b.scanTables = n <= scanTablesThreshold
135 0 :
136 0 : var data lsmview.Data
137 0 : data.Keys = b.sortedKeys
138 0 : data.Levels = make([]lsmview.Level, len(b.levels))
139 0 : for i, files := range b.levels {
140 0 : l := &data.Levels[i]
141 0 : l.Name = b.levelNames[i]
142 0 : l.Tables = make([]lsmview.Table, len(files))
143 0 : for j, f := range files {
144 0 : t := &l.Tables[j]
145 0 : if !f.Virtual {
146 0 : t.Label = fmt.Sprintf("%d", f.FileNum)
147 0 : } else {
148 0 : t.Label = fmt.Sprintf("%d (%d)", f.FileNum, f.FileBacking.DiskFileNum)
149 0 : }
150 :
151 0 : t.Size = f.Size
152 0 : t.SmallestKey = b.keys[string(f.Smallest.UserKey)]
153 0 : t.LargestKey = b.keys[string(f.Largest.UserKey)]
154 0 : t.Details = b.tableDetails(f, objProvider, newIters)
155 : }
156 : }
157 0 : return data
158 : }
159 :
160 : func (b *lsmViewBuilder) tableDetails(
161 : m *fileMetadata, objProvider objstorage.Provider, newIters tableNewIters,
162 0 : ) []string {
163 0 : res := make([]string, 0, 10)
164 0 : outf := func(format string, args ...any) {
165 0 : res = append(res, fmt.Sprintf(format, args...))
166 0 : }
167 :
168 0 : outf("%s: %s - %s", m.FileNum, m.Smallest.Pretty(b.fmtKey), m.Largest.Pretty(b.fmtKey))
169 0 : outf("size: %s", humanize.Bytes.Uint64(m.Size))
170 0 : 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 0 : outf("seqnums: %d - %d", m.SmallestSeqNum, m.LargestSeqNum)
184 0 : 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 0 : 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 0 : var iters iterSet
193 0 : if b.scanTables {
194 0 : var err error
195 0 : iters, err = newIters(context.Background(), m, nil /* opts */, internalIterOpts{}, iterPointKeys|iterRangeDeletions|iterRangeKeys)
196 0 : if err != nil {
197 0 : outf("error opening table: %v", err)
198 0 : } else {
199 0 : defer iters.CloseAll()
200 0 : }
201 : }
202 0 : const maxPoints = 14
203 0 : const maxRangeDels = 10
204 0 : const maxRangeKeys = 10
205 0 : if m.HasPointKeys {
206 0 : outf("points: %s - %s", m.SmallestPointKey.Pretty(b.fmtKey), m.LargestPointKey.Pretty(b.fmtKey))
207 0 : if b.scanTables {
208 0 : n := 0
209 0 : if it := iters.point; it != nil {
210 0 : for kv := it.First(); kv != nil; kv = it.Next() {
211 0 : if n == maxPoints {
212 0 : outf(" ...")
213 0 : break
214 : }
215 0 : outf(" %s", kv.K.Pretty(b.fmtKey))
216 0 : n++
217 : }
218 0 : if err := it.Error(); err != nil {
219 0 : outf(" error scanning points: %v", err)
220 0 : }
221 : }
222 0 : if n == 0 {
223 0 : outf(" no points")
224 0 : }
225 :
226 0 : n = 0
227 0 : 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 0 : if n == 0 {
246 0 : outf(" no range dels")
247 0 : }
248 : }
249 : }
250 0 : 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 0 : return res
277 : }
|