innodb_ruby 0.9.14 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +5 -6
  3. data/bin/innodb_log +13 -18
  4. data/bin/innodb_space +654 -778
  5. data/lib/innodb/checksum.rb +26 -24
  6. data/lib/innodb/data_dictionary.rb +490 -550
  7. data/lib/innodb/data_type.rb +362 -325
  8. data/lib/innodb/field.rb +102 -89
  9. data/lib/innodb/fseg_entry.rb +22 -26
  10. data/lib/innodb/history.rb +21 -21
  11. data/lib/innodb/history_list.rb +72 -76
  12. data/lib/innodb/ibuf_bitmap.rb +36 -36
  13. data/lib/innodb/ibuf_index.rb +6 -2
  14. data/lib/innodb/index.rb +245 -276
  15. data/lib/innodb/inode.rb +166 -124
  16. data/lib/innodb/list.rb +196 -183
  17. data/lib/innodb/log.rb +139 -110
  18. data/lib/innodb/log_block.rb +100 -91
  19. data/lib/innodb/log_group.rb +53 -64
  20. data/lib/innodb/log_reader.rb +97 -96
  21. data/lib/innodb/log_record.rb +328 -279
  22. data/lib/innodb/lsn.rb +86 -81
  23. data/lib/innodb/page/blob.rb +82 -83
  24. data/lib/innodb/page/fsp_hdr_xdes.rb +174 -165
  25. data/lib/innodb/page/ibuf_bitmap.rb +34 -34
  26. data/lib/innodb/page/index.rb +965 -924
  27. data/lib/innodb/page/index_compressed.rb +34 -34
  28. data/lib/innodb/page/inode.rb +103 -112
  29. data/lib/innodb/page/sys.rb +13 -15
  30. data/lib/innodb/page/sys_data_dictionary_header.rb +81 -59
  31. data/lib/innodb/page/sys_ibuf_header.rb +45 -42
  32. data/lib/innodb/page/sys_rseg_header.rb +88 -82
  33. data/lib/innodb/page/trx_sys.rb +204 -182
  34. data/lib/innodb/page/undo_log.rb +106 -92
  35. data/lib/innodb/page.rb +417 -414
  36. data/lib/innodb/record.rb +121 -164
  37. data/lib/innodb/record_describer.rb +66 -68
  38. data/lib/innodb/space.rb +381 -413
  39. data/lib/innodb/stats.rb +33 -35
  40. data/lib/innodb/system.rb +149 -171
  41. data/lib/innodb/undo_log.rb +129 -107
  42. data/lib/innodb/undo_record.rb +255 -247
  43. data/lib/innodb/util/buffer_cursor.rb +81 -79
  44. data/lib/innodb/util/read_bits_at_offset.rb +2 -1
  45. data/lib/innodb/version.rb +2 -2
  46. data/lib/innodb/xdes.rb +144 -142
  47. data/lib/innodb.rb +4 -5
  48. metadata +100 -25
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c2dd793281e3b83403e55c8d4e9104898d5df6099af80dd4453795a524e3fb32
4
+ data.tar.gz: d6ac28ab11759a618f9a84af611a705644f5f47f9c1cd387360b628bac88f527
5
+ SHA512:
6
+ metadata.gz: ffb5ba4b9805d4cb78aaecaec92adb4e6064d73a9349fbeb43fcc938b958adeb14f140747dd540f76d31bbf9a38fbde259bd99f3a9cc64f32a1456efc2006997
7
+ data.tar.gz: 9e98bd61fe4448fdbb15be66975581fe65d97c342685c21f2309ead8b0ed1fc217b08baf2855c336d8ea426e22b7c0556e0926064722e6cc23a95312a6a63ca4
data/README.md CHANGED
@@ -1,4 +1,7 @@
1
- # A parser for InnoDB file formats, in Ruby #
1
+ # A parser for InnoDB file formats, in Ruby
2
+
3
+ [![rspec test status](https://github.com/jeremycole/innodb_ruby/actions/workflows/rspec.yml/badge.svg)](https://github.com/jeremycole/innodb_ruby/actions/workflows/rspec.yml)
4
+ [![rubocop style check status](https://github.com/jeremycole/innodb_ruby/actions/workflows/rubocop.yml/badge.svg)](https://github.com/jeremycole/innodb_ruby/actions/workflows/rubocop.yml)
2
5
 
3
6
  The purpose for this library and tools is to expose some otherwise hidden internals of InnoDB. This code is not intended for critical production usage. It is definitely buggy, and it may be dangerous. Neither its internal APIs or its output are considered stable and are subject to change at any time.
4
7
 
@@ -11,11 +14,7 @@ It is intended as for a few purposes:
11
14
 
12
15
  Various parts of this library and the tools included may have wildly differing maturity levels, as it is worked on primarily based on immediate needs of the authors.
13
16
 
14
- # Resources #
17
+ # Resources
15
18
 
16
19
  * The [innodb_ruby wiki](https://github.com/jeremycole/innodb_ruby/wiki) contains some additional references and documentation to help you get started.
17
- * Visit the [innodb_ruby mailing list on Google Groups](https://groups.google.com/d/forum/innodb_ruby) or email [innodb_ruby@googlegroups.com](mailto:innodb_ruby@googlegroups.com) — If you have questions about `innodb_ruby` or its usage.
18
20
  * See the [RubyGems page for innodb_ruby](http://rubygems.org/gems/innodb_ruby) — Gem packaged releases are published regularly to RubyGems.org, which also provides online documentation.
19
-
20
-
21
- [![Build Status](https://travis-ci.org/jeremycole/innodb_ruby.svg?branch=master)](https://travis-ci.org/jeremycole/innodb_ruby)
data/bin/innodb_log CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- # -*- encoding : utf-8 -*-
2
+ # frozen_string_literal: true
3
3
 
4
4
  require "getoptlong"
5
5
  require "ostruct"
@@ -7,15 +7,9 @@ require "set"
7
7
  require "innodb"
8
8
 
9
9
  def log_summary(log_group)
10
- puts "%-20s%-15s%-10s%-12s%-10s" % [
11
- "lsn",
12
- "block",
13
- "length",
14
- "first_rec",
15
- "checkpoint",
16
- ]
10
+ puts "%-20s%-15s%-10s%-12s%-10s" % %w[lsn block length first_rec checkpoint]
17
11
  lsn = log_group.start_lsn.first
18
- log_group.each_block do |block_index, block|
12
+ log_group.each_block do |_block_index, block|
19
13
  header = block.header
20
14
  puts "%-20i%-15i%-10i%-12i%-10i" % [
21
15
  lsn,
@@ -30,14 +24,7 @@ def log_summary(log_group)
30
24
  end
31
25
 
32
26
  def log_reader_record_summary(reader, follow)
33
- puts "%-10s%-10s%-20s%-10s%-10s%-10s" % [
34
- "time",
35
- "lsn",
36
- "type",
37
- "size",
38
- "space",
39
- "page"
40
- ]
27
+ puts "%-10s%-10s%-20s%-10s%-10s%-10s" % %w[time lsn type size space page]
41
28
 
42
29
  reader.each_record(follow) do |rec|
43
30
  preamble = rec.preamble.dup
@@ -72,6 +59,7 @@ end
72
59
  def usage(exit_code, message = nil)
73
60
  print "Error: #{message}\n" unless message.nil?
74
61
 
62
+ # rubocop:disable Layout/HeredocIndentation
75
63
  print <<'END_OF_USAGE'
76
64
 
77
65
  Usage: innodb_log [-d] [-l <lsn>] -f <log file> <mode>
@@ -103,6 +91,7 @@ The following modes are supported:
103
91
  Dump the contents of a log record, using the Ruby pp ("pretty-print") module.
104
92
 
105
93
  END_OF_USAGE
94
+ # rubocop:enable Layout/HeredocIndentation
106
95
 
107
96
  exit exit_code
108
97
  end
@@ -112,12 +101,14 @@ end
112
101
  @options.dump = false
113
102
  @options.lsn = nil
114
103
 
104
+ # rubocop:disable Layout/SpaceInsideArrayLiteralBrackets
115
105
  getopt_options = [
116
106
  [ "--help", "-?", GetoptLong::NO_ARGUMENT ],
117
107
  [ "--log-file", "-f", GetoptLong::REQUIRED_ARGUMENT ],
118
108
  [ "--dump-blocks", "-d", GetoptLong::NO_ARGUMENT ],
119
109
  [ "--lsn", "-l", GetoptLong::REQUIRED_ARGUMENT ],
120
110
  ]
111
+ # rubocop:enable Layout/SpaceInsideArrayLiteralBrackets
121
112
 
122
113
  getopt = GetoptLong.new(*getopt_options)
123
114
 
@@ -136,6 +127,8 @@ end
136
127
 
137
128
  mode = ARGV.shift
138
129
 
130
+ # rubocop:disable Style/IfUnlessModifier
131
+
139
132
  unless mode
140
133
  usage 1, "At least one mode must be provided"
141
134
  end
@@ -144,10 +137,12 @@ if @options.log_files.empty?
144
137
  usage 1, "At least one log file (-f) must be specified"
145
138
  end
146
139
 
147
- if /^(log-)?record-/.match(mode) and !@options.lsn
140
+ if /^(log-)?record-/.match(mode) && !@options.lsn
148
141
  usage 1, "LSN must be specified using -l/--lsn"
149
142
  end
150
143
 
144
+ # rubocop:enable Style/IfUnlessModifier
145
+
151
146
  log_group = Innodb::LogGroup.new(@options.log_files.sort)
152
147
 
153
148
  case mode
data/bin/innodb_space CHANGED
@@ -1,27 +1,36 @@
1
1
  #!/usr/bin/env ruby
2
- # -*- encoding : utf-8 -*-
2
+ # frozen_string_literal: true
3
3
 
4
4
  require "getoptlong"
5
5
  require "ostruct"
6
+ require "histogram/array"
6
7
  require "innodb"
7
8
 
9
+ class String
10
+ def squish!
11
+ gsub!(/[[:space:]]+/, " ")
12
+ strip!
13
+ self
14
+ end
15
+
16
+ def squish
17
+ dup.squish!
18
+ end
19
+ end
20
+
8
21
  # Convert a floating point RGB array into an ANSI color number approximating it.
9
22
  def rgb_to_ansi(rgb)
10
23
  rgb_n = rgb.map { |c| (c * 5.0).round }
11
24
  16 + (rgb_n[0] * 36) + (rgb_n[1] * 6) + rgb_n[2]
12
25
  end
13
26
 
14
- def rgb_to_rgbhex(rgb)
15
- rgb.map { |c| "%02x" % [(c * 255.0).round] }.join
16
- end
17
-
18
27
  # Interpolate intermediate float-arrays between two float-arrays. Do not
19
28
  # include the points a and b in the result.
20
- def interpolate(a, b, count)
21
- deltas = a.each_index.map { |i| b[i] - a[i] }
22
- steps = a.each_index.map { |i| deltas[i].to_f / (count.to_f + 1) }
29
+ def interpolate(ary_a, ary_b, count)
30
+ deltas = ary_a.each_index.map { |i| ary_b[i] - ary_a[i] }
31
+ steps = ary_a.each_index.map { |i| deltas[i].to_f / (count.to_f + 1) }
23
32
 
24
- count.times.to_a.map { |i| a.each_index.map { |j| a[j] + ((i+1).to_f * steps[j]) } }
33
+ count.times.to_a.map { |i| ary_a.each_index.map { |j| ary_a[j] + ((i + 1).to_f * steps[j]) } }
25
34
  end
26
35
 
27
36
  # Interpolate intermediate float-arrays between each step in a sequence of
@@ -29,7 +38,7 @@ end
29
38
  def interpolate_sequence(sequence, count)
30
39
  result = []
31
40
  result << sequence.first
32
- (sequence.size-1).times.map { |n| [sequence[n], sequence[n+1]] }.each do |from, to|
41
+ (sequence.size - 1).times.map { |n| [sequence[n], sequence[n + 1]] }.each do |from, to|
33
42
  interpolate(from, to, count).each do |step|
34
43
  result << step
35
44
  end
@@ -48,15 +57,11 @@ HEATMAP_PROGRESSION = [
48
57
  [1.0, 1.0, 0.0], # Yellow
49
58
  [1.0, 0.0, 0.0], # Red
50
59
  [1.0, 0.0, 1.0], # Purple
51
- ]
60
+ ].freeze
52
61
 
53
62
  # Typical heatmap color progression.
54
63
  ANSI_COLORS_HEATMAP = interpolate_sequence(HEATMAP_PROGRESSION, 6).map { |rgb| rgb_to_ansi(rgb) }
55
64
 
56
- RGBHEX_COLORS_HEATMAP = interpolate_sequence(HEATMAP_PROGRESSION, 41).map { |rgb| rgb_to_rgbhex(rgb) }
57
-
58
- RGBHEX_COLORS_RANDOM = 100.times.inject([]) { |a, x| a << rgb_to_rgbhex([rand * 0.7 + 0.25, rand * 0.7 + 0.25, rand * 0.7 + 0.25]) }
59
-
60
65
  # The 24-step grayscale progression.
61
66
  ANSI_COLORS_GRAYSCALE = (0xe8..0xff).to_a
62
67
 
@@ -66,69 +71,47 @@ def ansi_color(color, text)
66
71
  end
67
72
 
68
73
  # Zero and 1/8 through 8/8 illustrations.
69
- BLOCK_CHARS_V = ["", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"]
70
- BLOCK_CHARS_H = ["", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"]
74
+ BLOCK_CHARS_V = "░▁▂▃▄▅▆▇█".chars.freeze
75
+ BLOCK_CHARS_H = "░▏▎▍▌▋▊▉█".chars.freeze
71
76
 
72
77
  # A reasonably large prime number to multiply identifiers by in order to
73
78
  # space out the colors used for similar identifiers.
74
- COLOR_SPACING_PRIME = 999983
79
+ COLOR_SPACING_PRIME = 999_983
75
80
 
76
81
  # Return a string with a possibly colored filled block for use in printing
77
82
  # to an ANSI-capable Unicode-enabled terminal.
78
- def filled_block(fraction, identifier=nil, block_chars=BLOCK_CHARS_V)
79
- if fraction == 0.0
83
+ def filled_block(fraction, identifier = nil, block_chars = BLOCK_CHARS_V)
84
+ if fraction.zero?
80
85
  block = block_chars[0]
81
86
  else
82
- parts = (fraction.to_f * (block_chars.size.to_f-1)).floor
83
- block = block_chars[[block_chars.size-1, parts+1].min]
87
+ parts = (fraction.to_f * (block_chars.size.to_f - 1)).floor
88
+ block = block_chars[[block_chars.size - 1, parts + 1].min]
84
89
  end
85
90
  if identifier
86
91
  # ANSI 256-color mode, color palette starts at 10 and contains 216 colors.
87
- color = 16 + ((identifier * COLOR_SPACING_PRIME) % 216)
92
+ color = 16 + ((identifier * COLOR_SPACING_PRIME) % 216)
88
93
  ansi_color(color, block)
89
94
  else
90
95
  block
91
96
  end
92
97
  end
93
98
 
94
- def svg_join_args(args)
95
- args.map { |arg| "%s=\"%s\"" % [ arg[0], arg[1], ] }.join(" ")
96
- end
99
+ def center(text, width)
100
+ return text if text.size >= width
97
101
 
98
- def svg(elem, args, content=nil)
99
- if content
100
- "<" + elem.to_s + " " + svg_join_args(args) + ">" +
101
- content.to_s +
102
- "</" + elem.to_s + ">"
103
- else
104
- "<" + elem.to_s + " " + svg_join_args(args) + " />"
105
- end
106
- end
107
-
108
- def svg_path_rounded_rect(x, y, w, h, r)
109
- [
110
- "M%i,%i" % [ x + r, y - r ],
111
- "L%i,%i" % [ x + w - r, y - r ],
112
- "S%i,%i %i,%i" % [ x + w + r, y - r, x + w + r, y + r ],
113
- "L%i,%i" % [ x + w + r, y + h - r ],
114
- "S%i,%i %i,%i" % [ x + w + r, y + h + r, x + w - r, y + h + r ],
115
- "L%i,%i" % [ x + r, y + h + r ],
116
- "S%i,%i %i,%i" % [ x - r, y + h + r, x - r, y + h - r ],
117
- "L%i,%i" % [ x - r, y + r ],
118
- "S%i,%i %i,%i" % [ x - r, y - r, x + r, y - r ],
119
- "Z",
120
- ].join(" ")
102
+ spaces = (width - text.size) / 2
103
+ (" " * spaces) + text + (" " * spaces)
121
104
  end
122
105
 
123
106
  # Print metadata about each list in an array of InnoDB::List objects.
124
107
  def print_lists(lists)
125
- puts "%-20s%-12s%-12s%-12s%-12s%-12s" % [
126
- "name",
127
- "length",
128
- "f_page",
129
- "f_offset",
130
- "l_page",
131
- "l_offset",
108
+ puts "%-20s%-12s%-12s%-12s%-12s%-12s" % %w[
109
+ name
110
+ length
111
+ f_page
112
+ f_offset
113
+ l_page
114
+ l_offset
132
115
  ]
133
116
 
134
117
  lists.each do |name, list|
@@ -146,33 +129,31 @@ end
146
129
  # Print a page usage bitmap for each extent descriptor in an array of
147
130
  # Innodb::XdesEntry objects.
148
131
  def print_xdes_list(space, list)
149
- puts "%-12s%-64s" % [
150
- "start_page",
151
- "page_used_bitmap"
132
+ puts "%-12s%-64s" % %w[
133
+ start_page
134
+ page_used_bitmap
152
135
  ]
153
136
 
154
137
  list.each do |entry|
155
- puts "%-12i%-64s" % [
156
- entry.xdes[:start_page],
157
- entry.each_page_status.inject("") { |bitmap, (page_number, page_status)|
158
- if page_number < space.pages
159
- bitmap += page_status[:free] ? "." : "#"
160
- end
161
- bitmap
162
- },
163
- ]
138
+ display = entry.each_page_status.inject("") do |bitmap, (page_number, page_status)|
139
+ if page_number < space.pages
140
+ bitmap += page_status[:free] ? "." : "#"
141
+ end
142
+ bitmap
143
+ end
144
+ puts "%-12i%-64s" % [entry.xdes[:start_page], display]
164
145
  end
165
146
  end
166
147
 
167
148
  # Print a summary of page usage for all pages in an index.
168
149
  def print_index_page_summary(pages)
169
- puts "%-12s%-8s%-8s%-8s%-8s%-8s" % [
170
- "page",
171
- "index",
172
- "level",
173
- "data",
174
- "free",
175
- "records",
150
+ puts "%-12s%-8s%-8s%-8s%-8s%-8s" % %w[
151
+ page
152
+ index
153
+ level
154
+ data
155
+ free
156
+ records
176
157
  ]
177
158
 
178
159
  pages.each do |page_number, page|
@@ -187,17 +168,17 @@ def print_index_page_summary(pages)
187
168
  page.records,
188
169
  ]
189
170
  when :ALLOCATED
190
- puts "%-12i%-8i%-8i%-8i%-8i%-8i" % [ page_number, 0, 0, 0, page.size, 0 ]
171
+ puts "%-12i%-8i%-8i%-8i%-8i%-8i" % [page_number, 0, 0, 0, page.size, 0]
191
172
  end
192
173
  end
193
174
  end
194
175
 
195
176
  # Print a summary of all spaces in the InnoDB system.
196
177
  def system_spaces(innodb_system)
197
- puts "%-32s%-12s%-12s" % [
198
- "name",
199
- "pages",
200
- "indexes",
178
+ puts "%-32s%-12s%-12s" % %w[
179
+ name
180
+ pages
181
+ indexes
201
182
  ]
202
183
 
203
184
  print_space_information = lambda do |name, space|
@@ -213,6 +194,7 @@ def system_spaces(innodb_system)
213
194
  innodb_system.each_table_name do |table_name|
214
195
  space = innodb_system.space_by_table_name(table_name)
215
196
  next unless space
197
+
216
198
  print_space_information.call(table_name, space)
217
199
  end
218
200
 
@@ -223,15 +205,15 @@ end
223
205
 
224
206
  # Print the contents of the SYS_TABLES data dictionary table.
225
207
  def data_dictionary_tables(innodb_system)
226
- puts "%-32s%-12s%-12s%-12s%-12s%-12s%-15s%-12s" % [
227
- "name",
228
- "id",
229
- "n_cols",
230
- "type",
231
- "mix_id",
232
- "mix_len",
233
- "cluster_name",
234
- "space",
208
+ puts "%-32s%-12s%-12s%-12s%-12s%-12s%-15s%-12s" % %w[
209
+ name
210
+ id
211
+ n_cols
212
+ type
213
+ mix_id
214
+ mix_len
215
+ cluster_name
216
+ space
235
217
  ]
236
218
 
237
219
  innodb_system.data_dictionary.each_table do |record|
@@ -250,14 +232,14 @@ end
250
232
 
251
233
  # Print the contents of the SYS_COLUMNS data dictionary table.
252
234
  def data_dictionary_columns(innodb_system)
253
- puts "%-12s%-6s%-32s%-12s%-12s%-6s%-6s" % [
254
- "table_id",
255
- "pos",
256
- "name",
257
- "mtype",
258
- "prtype",
259
- "len",
260
- "prec",
235
+ puts "%-12s%-6s%-32s%-12s%-12s%-6s%-6s" % %w[
236
+ table_id
237
+ pos
238
+ name
239
+ mtype
240
+ prtype
241
+ len
242
+ prec
261
243
  ]
262
244
 
263
245
  innodb_system.data_dictionary.each_column do |record|
@@ -275,14 +257,14 @@ end
275
257
 
276
258
  # Print the contents of the SYS_INDEXES data dictionary table.
277
259
  def data_dictionary_indexes(innodb_system)
278
- puts "%-12s%-12s%-32s%-10s%-6s%-12s%-12s" % [
279
- "table_id",
280
- "id",
281
- "name",
282
- "n_fields",
283
- "type",
284
- "space",
285
- "page_no",
260
+ puts "%-12s%-12s%-32s%-10s%-6s%-12s%-12s" % %w[
261
+ table_id
262
+ id
263
+ name
264
+ n_fields
265
+ type
266
+ space
267
+ page_no
286
268
  ]
287
269
 
288
270
  innodb_system.data_dictionary.each_index do |record|
@@ -300,10 +282,10 @@ end
300
282
 
301
283
  # Print the contents of the SYS_FIELDS data dictionary table.
302
284
  def data_dictionary_fields(innodb_system)
303
- puts "%-12s%-12s%-32s" % [
304
- "index_id",
305
- "pos",
306
- "col_name",
285
+ puts "%-12s%-12s%-32s" % %w[
286
+ index_id
287
+ pos
288
+ col_name
307
289
  ]
308
290
 
309
291
  innodb_system.data_dictionary.each_field do |record|
@@ -316,12 +298,12 @@ def data_dictionary_fields(innodb_system)
316
298
  end
317
299
 
318
300
  def space_summary(space, start_page)
319
- puts "%-12s%-20s%-12s%-12s%-20s" % [
320
- "page",
321
- "type",
322
- "prev",
323
- "next",
324
- "lsn",
301
+ puts "%-12s%-20s%-12s%-12s%-20s" % %w[
302
+ page
303
+ type
304
+ prev
305
+ next
306
+ lsn
325
307
  ]
326
308
 
327
309
  space.each_page(start_page) do |page_number, page|
@@ -339,12 +321,16 @@ def space_index_pages_summary(space, start_page)
339
321
  print_index_page_summary(space.each_page(start_page))
340
322
  end
341
323
 
324
+ def space_index_fseg_pages_summary(space, fseg_id)
325
+ print_index_page_summary(space.inode(fseg_id).each_page)
326
+ end
327
+
342
328
  def space_page_type_regions(space, start_page)
343
- puts "%-12s%-12s%-12s%-20s" % [
344
- "start",
345
- "end",
346
- "count",
347
- "type",
329
+ puts "%-12s%-12s%-12s%-20s" % %w[
330
+ start
331
+ end
332
+ count
333
+ type
348
334
  ]
349
335
 
350
336
  space.each_page_type_region(start_page) do |region|
@@ -363,16 +349,16 @@ def space_page_type_summary(space, start_page)
363
349
  page_count = 0
364
350
  # A Hash of page type => count.
365
351
  page_type = Hash.new(0)
366
- space.each_page(start_page) do |page_number, page|
352
+ space.each_page(start_page) do |_page_number, page|
367
353
  page_count += 1
368
354
  page_type[page.type] += 1
369
355
  end
370
356
 
371
- puts "%-20s%-12s%-12s%-20s" % [
372
- "type",
373
- "count",
374
- "percent",
375
- "description",
357
+ puts "%-20s%-12s%-12s%-20s" % %w[
358
+ type
359
+ count
360
+ percent
361
+ description
376
362
  ]
377
363
 
378
364
  # Sort the page type Hash by count, descending.
@@ -380,7 +366,7 @@ def space_page_type_summary(space, start_page)
380
366
  puts "%-20s%-12i%-12.2f%-20s" % [
381
367
  type,
382
368
  type_count,
383
- 100.0 * (type_count.to_f / page_count.to_f),
369
+ 100.0 * (type_count.to_f / page_count),
384
370
  Innodb::Page::PAGE_TYPE[type][:description],
385
371
  ]
386
372
  end
@@ -393,16 +379,14 @@ end
393
379
  def space_list_iterate(space, list_name)
394
380
  fsp = space.page(0).fsp_header
395
381
 
396
- unless fsp[list_name] && fsp[list_name].is_a?(Innodb::List)
397
- raise "List '#{list_name}' doesn't exist"
398
- end
382
+ raise "List '#{list_name}' doesn't exist" unless fsp[list_name].is_a?(Innodb::List)
399
383
 
400
384
  case fsp[list_name]
401
385
  when Innodb::List::Xdes
402
386
  print_xdes_list(space, fsp[list_name])
403
387
  when Innodb::List::Inode
404
- puts "%-12s" % [
405
- "page",
388
+ puts "%-12s" % %w[
389
+ page
406
390
  ]
407
391
  fsp[list_name].each do |page|
408
392
  puts "%-12i" % [
@@ -413,23 +397,25 @@ def space_list_iterate(space, list_name)
413
397
  end
414
398
 
415
399
  def space_indexes(innodb_system, space)
416
- puts "%-12s%-32s%-12s%-12s%-12s%-12s%-12s" % [
417
- "id",
418
- "name",
419
- "root",
420
- "fseg",
421
- "used",
422
- "allocated",
423
- "fill_factor",
400
+ puts "%-12s%-32s%-12s%-12s%-12s%-12s%-12s%-12s" % %w[
401
+ id
402
+ name
403
+ root
404
+ fseg
405
+ fseg_id
406
+ used
407
+ allocated
408
+ fill_factor
424
409
  ]
425
410
 
426
411
  space.each_index do |index|
427
412
  index.each_fseg do |fseg_name, fseg|
428
- puts "%-12i%-32s%-12i%-12s%-12i%-12i%-12s" % [
413
+ puts "%-12i%-32s%-12i%-12s%-12i%-12i%-12i%-12s" % [
429
414
  index.id,
430
415
  innodb_system ? innodb_system.index_name_by_id(index.id) : "",
431
416
  index.root.offset,
432
417
  fseg_name,
418
+ fseg.fseg_id,
433
419
  fseg.used_pages,
434
420
  fseg.total_pages,
435
421
  "%.2f%%" % fseg.fill_factor,
@@ -438,17 +424,15 @@ def space_indexes(innodb_system, space)
438
424
  end
439
425
  end
440
426
 
441
- def space_index_pages_free_plot(space, image, start_page)
442
- unless require "gnuplot"
443
- raise "Couldn't load gnuplot. Is it installed?"
444
- end
427
+ def space_index_pages_free_plot(space, start_page)
428
+ raise "Could not load gnuplot. Is it installed?" unless require "gnuplot"
445
429
 
446
- index_data = {0 => {:x => [], :y => []}}
430
+ index_data = { 0 => { x: [], y: [] } }
447
431
 
448
432
  space.each_page(start_page) do |page_number, page|
449
433
  case page.type
450
434
  when :INDEX
451
- data = (index_data[page.page_header[:index_id]] ||= {:x => [], :y => []})
435
+ data = (index_data[page.page_header[:index_id]] ||= { x: [], y: [] })
452
436
  data[:x] << page_number
453
437
  data[:y] << page.free_space
454
438
  when :ALLOCATED
@@ -457,15 +441,17 @@ def space_index_pages_free_plot(space, image, start_page)
457
441
  end
458
442
  end
459
443
 
460
- image_file = image + "_free.png"
444
+ image_name = space.name.sub(".ibd", "").gsub(/[^a-zA-Z0-9_]/, "_").sub(/\A_+/, "")
445
+ image_file = "#{image_name}_free.png"
446
+
461
447
  # Aim for one horizontal pixel per extent, but min 1k and max 10k width.
462
- image_width = [10000, [1000, space.pages / space.pages_per_extent].max].min
448
+ image_width = [10_000, [1_000, space.pages / space.pages_per_extent].max].min
463
449
 
464
450
  Gnuplot.open do |gp|
465
451
  Gnuplot::Plot.new(gp) do |plot|
466
452
  plot.terminal "png size #{image_width}, 800"
467
453
  plot.output image_file
468
- plot.title image
454
+ plot.title image_name.gsub("_", " ")
469
455
  plot.key "reverse left top box horizontal Left textcolor variable"
470
456
  plot.ylabel "free space per page"
471
457
  plot.xlabel "page number"
@@ -475,7 +461,7 @@ def space_index_pages_free_plot(space, image, start_page)
475
461
  index_data.sort.each do |id, data|
476
462
  plot.data << Gnuplot::DataSet.new([data[:x], data[:y]]) do |ds|
477
463
  ds.with = "dots"
478
- ds.title = id == 0 ? "Unallocated" : "Index #{id}"
464
+ ds.title = id.zero? ? "Unallocated" : "Index #{id}"
479
465
  end
480
466
  end
481
467
 
@@ -488,279 +474,99 @@ def space_extents(space)
488
474
  print_xdes_list(space, space.each_xdes)
489
475
  end
490
476
 
477
+ # rubocop:disable Metrics/BlockNesting
478
+ def space_extents_illustrate_page_status(space, entry, count_by_identifier, identifiers)
479
+ entry.each_page_status.each_with_object("".dup) do |(page_number, page_status), bitmap|
480
+ if page_number >= space.pages
481
+ bitmap << " "
482
+ next
483
+ end
484
+
485
+ used_fraction = 1.0
486
+ identifier = nil
487
+ if page_status[:free]
488
+ used_fraction = 0.0
489
+ else
490
+ page = space.page(page_number)
491
+ used_fraction = page.used_space.to_f / page.size if page.respond_to?(:used_space)
492
+ if page.respond_to?(:index_id)
493
+ identifier = page.index_id
494
+ unless identifiers[identifier]
495
+ identifiers[identifier] = page.ibuf_index? ? "Insert Buffer Index" : "Index #{page.index_id}"
496
+ if space.innodb_system
497
+ table, index = space.innodb_system.table_and_index_name_by_id(page.index_id)
498
+ identifiers[identifier] += " (%s.%s)" % [table, index] if table && index
499
+ end
500
+ end
501
+ end
502
+ end
503
+
504
+ bitmap << filled_block(used_fraction, identifier)
505
+
506
+ if used_fraction.zero?
507
+ count_by_identifier[:free] += 1
508
+ else
509
+ count_by_identifier[identifier] += 1
510
+ end
511
+ end
512
+ end
513
+ # rubocop:enable Metrics/BlockNesting
514
+
491
515
  # Illustrate the space by printing each extent and for each page, printing a
492
516
  # filled block colored based on the index the page is part of. Print a legend
493
517
  # for the colors used afterwards.
494
518
  def space_extents_illustrate(space)
495
- line_width = space.pages_per_extent
519
+ width = space.pages_per_extent
496
520
  puts
497
- puts "%12s ╭%-#{line_width}s" % [ "Start Page", "─" * line_width ]
521
+ puts "%12s %-#{width}s " % ["", center(space.name, width)]
522
+ puts "%12s ╭%-#{width}s╮" % ["Start Page", "─" * width]
498
523
 
499
524
  identifiers = {}
500
525
  count_by_identifier = Hash.new(0)
501
526
 
502
527
  space.each_xdes do |entry|
503
- puts "%12i │%-#{line_width}s│" % [
528
+ puts "%12i │%-#{width}s│" % [
504
529
  entry.xdes[:start_page],
505
- entry.each_page_status.inject("") { |bitmap, (page_number, page_status)|
506
- if page_number < space.pages
507
- used_fraction = 1.0
508
- identifier = nil
509
- if page_status[:free]
510
- used_fraction = 0.0
511
- else
512
- page = space.page(page_number)
513
- if page.respond_to?(:used_space)
514
- used_fraction = page.used_space.to_f / page.size.to_f
515
- end
516
- if page.respond_to?(:index_id)
517
- identifier = page.index_id
518
- unless identifiers[identifier]
519
- identifiers[identifier] = (page.index_id == Innodb::IbufIndex::INDEX_ID) ?
520
- "Insert Buffer Index" :
521
- "Index #{page.index_id}"
522
- if space.innodb_system
523
- table, index = space.innodb_system.table_and_index_name_by_id(page.index_id)
524
- if table && index
525
- identifiers[identifier] += " (%s.%s)" % [table, index]
526
- end
527
- end
528
- end
529
- end
530
- end
531
- bitmap += filled_block(used_fraction, identifier)
532
- if used_fraction != 0.0
533
- count_by_identifier[identifier] += 1
534
- else
535
- count_by_identifier[:free] += 1
536
- end
537
- else
538
- bitmap += " "
539
- end
540
- bitmap
541
- },
530
+ space_extents_illustrate_page_status(space, entry, count_by_identifier, identifiers),
542
531
  ]
543
532
  end
544
533
  total_pages = count_by_identifier.values.reduce(:+)
545
534
 
546
- puts "%12s ╰%-#{line_width}s╯" % [ "", "─" * line_width ]
535
+ puts "%12s ╰%-#{width}s╯" % ["", "─" * width]
547
536
 
548
537
  puts
549
538
  puts "Legend (%s = 1 page):" % [filled_block(1.0, nil)]
550
539
  puts " %-62s %8s %8s" % [
551
- "Page Type", "Pages", "Ratio"
540
+ "Page Type",
541
+ "Pages",
542
+ "Ratio",
552
543
  ]
553
544
  puts " %s %-60s %8i %7.2f%%" % [
554
545
  filled_block(1.0, nil),
555
546
  "System",
556
547
  count_by_identifier[nil],
557
- 100.0 * (count_by_identifier[nil].to_f / total_pages.to_f),
548
+ 100.0 * (count_by_identifier[nil].to_f / total_pages),
558
549
  ]
559
550
  identifiers.sort.each do |identifier, description|
560
551
  puts " %s %-60s %8i %7.2f%%" % [
561
552
  filled_block(1.0, identifier),
562
553
  description,
563
554
  count_by_identifier[identifier],
564
- 100.0 * (count_by_identifier[identifier].to_f / total_pages.to_f),
555
+ 100.0 * (count_by_identifier[identifier].to_f / total_pages),
565
556
  ]
566
557
  end
567
558
  puts " %s %-60s %8i %7.2f%%" % [
568
559
  filled_block(0.0, nil),
569
560
  "Free space",
570
561
  count_by_identifier[:free],
571
- 100.0 * (count_by_identifier[:free].to_f / total_pages.to_f),
562
+ 100.0 * (count_by_identifier[:free].to_f / total_pages),
572
563
  ]
573
564
  puts
574
565
  end
575
566
 
576
- def svg_extent_legend(x, y, block_size, color=nil, description=nil, pages=nil, ratio=nil)
577
- [
578
- svg("rect", {
579
- "y" => y,
580
- "x" => x,
581
- "width" => block_size,
582
- "height" => block_size,
583
- "fill" => color ? color : "white",
584
- "stroke" => description ? "black" : "none",
585
- }),
586
- svg("text", {
587
- "y" => y + block_size - 4,
588
- "x" => x + (description ? block_size + 5 : 0),
589
- "font-family" => "monospace",
590
- "font-size" => block_size,
591
- "font-weight" => description ? "normal" : "bold",
592
- "text-anchor" => "start",
593
- }, description ? description : "Page Type"),
594
- svg("text", {
595
- "y" => y + block_size - 4,
596
- "x" => x + block_size + 5 + (40 * block_size),
597
- "font-family" => "monospace",
598
- "font-size" => block_size,
599
- "font-weight" => description ? "normal" : "bold",
600
- "text-anchor" => "end",
601
- }, pages ? pages : "Pages"),
602
- svg("text", {
603
- "y" => y + block_size - 4,
604
- "x" => x + block_size + 5 + (40 * block_size) + (10 * block_size),
605
- "font-family" => "monospace",
606
- "font-size" => block_size,
607
- "font-weight" => description ? "normal" : "bold",
608
- "text-anchor" => "end",
609
- }, ratio ? ("%7.2f%%" % [ratio]) : "Ratio"),
610
- ].join("\n")
611
- end
612
-
613
- # Illustrate the space by printing each extent and for each page, printing a
614
- # filled block colored based on the index the page is part of. Print a legend
615
- # for the colors used afterwards.
616
- def space_extents_illustrate_svg(space)
617
- line_width = space.pages_per_extent
618
- block_size = @options.illustration_block_size
619
-
620
- puts "<?xml version=\"1.0\"?>"
621
- puts "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
622
-
623
- identifiers = {}
624
- count_by_identifier = Hash.new(0)
625
-
626
- graphic_x = 48
627
- graphic_y = 16
628
-
629
- puts svg("text", {
630
- "y" => graphic_y - 3,
631
- "x" => graphic_x - 7,
632
- "font-family" => "monospace",
633
- "font-size" => block_size,
634
- "font-weight" => "bold",
635
- "text-anchor" => "end",
636
- }, "Page")
637
-
638
- block_x = 0
639
- block_y = 0
640
- space.each_xdes do |entry|
641
- block_x = 0
642
-
643
- puts svg("text", {
644
- "y" => graphic_y + block_y + block_size,
645
- "x" => graphic_x - 7,
646
- "font-family" => "monospace",
647
- "font-size" => block_size,
648
- "text-anchor" => "end",
649
- }, entry.xdes[:start_page])
650
-
651
- entry.each_page_status do |page_number, page_status|
652
- if page_number < space.pages
653
- used_fraction = 1.0
654
- identifier = nil
655
- if page_status[:free]
656
- used_fraction = 0.0
657
- else
658
- page = space.page(page_number)
659
- if page.respond_to?(:used_space)
660
- used_fraction = page.used_space.to_f / page.size.to_f
661
- end
662
- if page.respond_to?(:index_id)
663
- identifier = page.index_id
664
- unless identifiers[identifier]
665
- identifiers[identifier] = (page.index_id == Innodb::IbufIndex::INDEX_ID) ?
666
- "Insert Buffer Index" :
667
- "Index #{page.index_id}"
668
- if space.innodb_system
669
- table, index = space.innodb_system.table_and_index_name_by_id(page.index_id)
670
- if table && index
671
- identifiers[identifier] += " (%s.%s)" % [table, index]
672
- end
673
- end
674
- end
675
- end
676
- end
677
- if used_fraction != 0.0
678
- count_by_identifier[identifier] += 1
679
- else
680
- count_by_identifier[:free] += 1
681
- end
682
-
683
- block_height = block_size * used_fraction
684
- color = "black"
685
- if identifier
686
- color = "#" + RGBHEX_COLORS_RANDOM[(identifier * COLOR_SPACING_PRIME) % RGBHEX_COLORS_RANDOM.size]
687
- end
688
- puts svg("rect", {
689
- "x" => graphic_x + block_x,
690
- "y" => graphic_y + block_y + (block_size - block_height),
691
- "width" => block_size,
692
- "height" => block_height,
693
- "fill" => color,
694
- })
695
- end
696
- block_x += block_size
697
- end
698
- block_y += block_size
699
- end
700
-
701
- puts svg("path", {
702
- "stroke" => "black",
703
- "stroke-width" => 1,
704
- "fill" => "none",
705
- "d" => svg_path_rounded_rect(
706
- graphic_x,
707
- graphic_y,
708
- block_x,
709
- block_y,
710
- 4
711
- ),
712
- })
713
-
714
- block_x = 0
715
- block_y += 10
716
- puts svg_extent_legend(
717
- graphic_x + block_x,
718
- graphic_y + block_y,
719
- block_size
720
- )
721
- block_y += block_size + 2
722
-
723
- puts svg_extent_legend(
724
- graphic_x + block_x,
725
- graphic_y + block_y,
726
- block_size,
727
- "black",
728
- "System",
729
- count_by_identifier[nil],
730
- 100.0 * (count_by_identifier[nil].to_f / space.pages.to_f)
731
- )
732
- block_y += block_size + 2
733
-
734
- identifiers.sort.each do |identifier, description|
735
- puts svg_extent_legend(
736
- graphic_x + block_x,
737
- graphic_y + block_y,
738
- block_size,
739
- "#" + RGBHEX_COLORS_RANDOM[(identifier * COLOR_SPACING_PRIME) % RGBHEX_COLORS_RANDOM.size],
740
- description,
741
- count_by_identifier[identifier],
742
- 100.0 * (count_by_identifier[identifier].to_f / space.pages.to_f)
743
- )
744
- block_y += block_size + 2
745
- end
746
-
747
- puts svg_extent_legend(
748
- graphic_x + block_x,
749
- graphic_y + block_y,
750
- block_size,
751
- "white",
752
- "Free space",
753
- count_by_identifier[:free],
754
- 100.0 * (count_by_identifier[:free].to_f / space.pages.to_f)
755
- )
756
-
757
- puts "</svg>"
758
- end
759
-
760
-
761
567
  def space_lsn_age_illustrate(space)
762
568
  colors = ANSI_COLORS_HEATMAP
763
- line_width = @options.illustration_line_width
569
+ width = @options.illustration_line_width
764
570
 
765
571
  # Calculate the minimum and maximum LSN in the space. This is pretty
766
572
  # inefficient as we end up scanning all pages twice.
@@ -768,70 +574,57 @@ def space_lsn_age_illustrate(space)
768
574
 
769
575
  lsn_min = lsn_max = space.page(0).lsn
770
576
  space.each_page do |page_number, page|
771
- if page.lsn != 0
772
- page_lsn[page_number] = page.lsn
773
- lsn_min = page.lsn < lsn_min ? page.lsn : lsn_min
774
- lsn_max = page.lsn > lsn_max ? page.lsn : lsn_max
775
- end
577
+ next if page.lsn.zero?
578
+
579
+ page_lsn[page_number] = page.lsn
580
+ lsn_min = page.lsn < lsn_min ? page.lsn : lsn_min
581
+ lsn_max = page.lsn > lsn_max ? page.lsn : lsn_max
776
582
  end
777
583
  lsn_delta = lsn_max - lsn_min
778
584
 
779
585
  puts
780
- puts "%12s ╭%-#{line_width}s" % [ "Start Page", "─" * line_width ]
586
+ puts "%12s %-#{width}s " % ["", center(space.name, width)]
587
+ puts "%12s ╭%-#{width}s╮" % ["Start Page", "─" * width]
781
588
 
782
589
  start_page = 0
783
- page_lsn.each_slice(line_width) do |slice|
784
- puts "%12i │%-#{line_width}s│" % [
590
+ page_lsn.each_slice(width) do |slice|
591
+ puts "%12i │%-#{width}s│" % [
785
592
  start_page,
786
- slice.inject("") { |line, lsn|
593
+ slice.inject("") do |line, lsn|
787
594
  if lsn
788
- age_ratio = (lsn - lsn_min).to_f / lsn_delta.to_f
595
+ age_ratio = (lsn - lsn_min).to_f / lsn_delta
789
596
  color = colors[(age_ratio * colors.size.to_f).floor]
790
597
  line += ansi_color(color, filled_block(1.0, nil))
791
598
  else
792
599
  line += " "
793
600
  end
794
601
  line
795
- },
602
+ end,
796
603
  ]
797
- start_page += line_width
604
+ start_page += width
798
605
  end
799
606
 
800
- puts "%12s ╰%-#{line_width}s╯" % [ "", "─" * line_width ]
607
+ puts "%12s ╰%-#{width}s╯" % ["", "─" * width]
801
608
 
802
- lsn_legend = "<" + ("─" * (colors.size - 2)) + ">"
609
+ _, lsn_freq = page_lsn.reject(&:nil?).histogram(colors.size, min: lsn_min, max: lsn_max)
610
+ lsn_freq_delta = lsn_freq.max - lsn_freq.min
803
611
 
804
- begin
805
- # Try to optionally replace the boring lsn_legend with a histogram of
806
- # page age distribution. If histogram/array is not available, move on.
807
-
808
- require 'histogram/array'
809
-
810
- lsn_bins, lsn_freq = page_lsn.select { |lsn| !lsn.nil? }.
811
- histogram(colors.size, :min => lsn_min, :max => lsn_max)
812
-
813
- lsn_freq_delta = lsn_freq.max - lsn_freq.min
814
-
815
- lsn_legend = ""
816
- lsn_freq.each do |freq|
817
- freq_norm = freq / lsn_freq_delta
818
- if freq_norm > 0.0
819
- lsn_legend << filled_block(freq_norm)
820
- else
821
- # Avoid the "empty" block used for 0.0.
822
- lsn_legend << " "
823
- end
824
- end
825
- rescue LoadError
826
- # That's okay! Leave the legend boring.
612
+ lsn_age_histogram = "".dup
613
+ lsn_freq.each do |freq|
614
+ freq_norm = freq / lsn_freq_delta
615
+ lsn_age_histogram << (freq_norm > 0.0 ? filled_block(freq_norm) : " ")
827
616
  end
828
617
 
829
618
  puts
830
- puts "Legend (%s = 1 page):" % [filled_block(1.0, nil)]
619
+ puts "LSN Age Histogram (%s = ~%d pages):" % [
620
+ filled_block(1.0, nil),
621
+ (space.pages.to_f / colors.size).round,
622
+ ]
831
623
  puts " %12s %s %-12s" % [
832
624
  "Min LSN",
833
- lsn_legend,
834
- "Max LSN" ]
625
+ lsn_age_histogram,
626
+ "Max LSN",
627
+ ]
835
628
  puts " %12i %s %-12i" % [
836
629
  lsn_min,
837
630
  colors.map { |c| ansi_color(c, filled_block(1.0, nil)) }.join,
@@ -839,126 +632,6 @@ def space_lsn_age_illustrate(space)
839
632
  ]
840
633
  end
841
634
 
842
- def space_lsn_age_illustrate_svg(space)
843
- colors = RGBHEX_COLORS_HEATMAP
844
- line_width = @options.illustration_line_width
845
- block_size = @options.illustration_block_size
846
-
847
- puts "<?xml version=\"1.0\"?>"
848
- puts "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
849
-
850
- # Calculate the minimum and maximum LSN in the space. This is pretty
851
- # inefficient as we end up scanning all pages twice.
852
- page_lsn = Array.new(space.pages)
853
-
854
- lsn_min = lsn_max = space.page(0).lsn
855
- space.each_page do |page_number, page|
856
- if page.lsn != 0
857
- page_lsn[page_number] = page.lsn
858
- lsn_min = page.lsn < lsn_min ? page.lsn : lsn_min
859
- lsn_max = page.lsn > lsn_max ? page.lsn : lsn_max
860
- end
861
- end
862
- lsn_delta = lsn_max - lsn_min
863
-
864
- graphic_x = 48
865
- graphic_y = 16
866
-
867
- block_x = 0
868
- block_y = 0
869
-
870
- puts svg("text", {
871
- "y" => graphic_y - 3,
872
- "x" => graphic_x - 7,
873
- "font-family" => "monospace",
874
- "font-size" => block_size,
875
- "font-weight" => "bold",
876
- "text-anchor" => "end",
877
- }, "Page")
878
-
879
- start_page = 0
880
- page_lsn.each_slice(line_width) do |slice|
881
- block_x = 0
882
- slice.each do |lsn|
883
- rgbhex = ""
884
- if lsn
885
- age_ratio = (lsn - lsn_min).to_f / lsn_delta.to_f
886
- color = colors[(age_ratio * colors.size.to_f).floor]
887
- end
888
- puts svg("rect", {
889
- "y" => graphic_y + block_y,
890
- "x" => graphic_x + block_x,
891
- "width" => block_size,
892
- "height" => block_size,
893
- "fill" => color ? "#" + color : "black",
894
- })
895
- block_x += block_size
896
- end
897
- puts svg("text", {
898
- "y" => graphic_y + block_y + block_size,
899
- "x" => graphic_x - 7,
900
- "font-family" => "monospace",
901
- "font-size" => block_size,
902
- "text-anchor" => "end",
903
- }, start_page)
904
- block_y += block_size
905
- start_page += line_width
906
- end
907
-
908
- puts svg("path", {
909
- "stroke" => "black",
910
- "stroke-width" => 1,
911
- "fill" => "none",
912
- "d" => svg_path_rounded_rect(
913
- graphic_x,
914
- graphic_y,
915
- line_width * block_size,
916
- block_y,
917
- 4
918
- ),
919
- })
920
-
921
- block_x = 0
922
- block_y += 16
923
- puts svg("text", {
924
- "y" => graphic_y + block_y + block_size - 4,
925
- "x" => graphic_x + block_x,
926
- "font-family" => "monospace",
927
- "font-size" => block_size,
928
- "text-anchor" => "start",
929
- }, lsn_min)
930
- color_width = ((64.0 * block_size.to_f) / colors.size.to_f).round
931
- colors.each do |color|
932
- puts svg("rect", {
933
- "y" => graphic_y + block_y + block_size,
934
- "x" => graphic_x + block_x,
935
- "width" => color_width,
936
- "height" => block_size,
937
- "fill" => "#" + color,
938
- })
939
- block_x += color_width
940
- end
941
- puts svg("text", {
942
- "y" => graphic_y + block_y + block_size - 4,
943
- "x" => graphic_x + block_x,
944
- "font-family" => "monospace",
945
- "font-size" => block_size,
946
- "text-anchor" => "end",
947
- }, lsn_max)
948
-
949
- puts svg("text", {
950
- "y" => graphic_y + block_y + block_size - 4,
951
- "x" => graphic_x + (block_x / 2),
952
- "font-family" => "monospace",
953
- "font-weight" => "bold",
954
- "font-size" => block_size,
955
- "text-anchor" => "middle",
956
- }, "LSN Age")
957
-
958
-
959
- puts "</svg>\n"
960
- end
961
-
962
635
  def print_inode_summary(inode)
963
636
  puts "INODE fseg_id=%d, pages=%d, frag=%d, full=%d, not_full=%d, free=%d" % [
964
637
  inode.fseg_id,
@@ -971,6 +644,7 @@ def print_inode_summary(inode)
971
644
  end
972
645
 
973
646
  def print_inode_detail(inode)
647
+ # rubocop:disable Layout/LineLength
974
648
  puts "INODE fseg_id=%d, pages=%d, frag=%d pages (%s), full=%d extents (%s), not_full=%d extents (%s) (%d/%d pages used), free=%d extents (%s)" % [
975
649
  inode.fseg_id,
976
650
  inode.total_pages,
@@ -985,6 +659,13 @@ def print_inode_detail(inode)
985
659
  inode.free.length,
986
660
  inode.free.each.to_a.map { |x| "#{x.start_page}-#{x.end_page}" }.join(", "),
987
661
  ]
662
+ # rubocop:enable Layout/LineLength
663
+ end
664
+
665
+ def space_inodes_fseg_id(space)
666
+ space.each_inode do |inode|
667
+ puts inode.fseg_id
668
+ end
988
669
  end
989
670
 
990
671
  def space_inodes_summary(space)
@@ -1015,42 +696,6 @@ def page_account(innodb_system, space, page_number)
1015
696
  page_type[:usage],
1016
697
  ]
1017
698
 
1018
- if page.corrupt?
1019
- puts " Page appears to be corrupt."
1020
- puts " Stored checksum is %d, type %s." % [
1021
- page.checksum,
1022
- page.checksum_type ? page.checksum_type : "unknown",
1023
- ]
1024
- puts " Calculated checksums:"
1025
- puts " crc32 %d" % [page.checksum_crc32]
1026
- puts " innodb %d" % [page.checksum_innodb]
1027
- end
1028
-
1029
- if page.torn?
1030
- puts " Page appears to be torn."
1031
- puts " Full LSN from header is %d." % [page.lsn]
1032
- puts " Low 32 bits of LSN from header is %d, trailer is %d." % [
1033
- page.lsn_low32_header,
1034
- page.lsn_low32_trailer,
1035
- ]
1036
- end
1037
-
1038
- if page.misplaced?
1039
- puts " Page appears to be misplaced."
1040
- if page.misplaced_offset?
1041
- puts " Requested page %d but offset stored in page is %d." % [
1042
- page_number,
1043
- page.offset,
1044
- ]
1045
- end
1046
- if page.misplaced_space?
1047
- puts " Space's ID %d does not match page's stored space ID %d." % [
1048
- page.space.space_id,
1049
- page.space_id,
1050
- ]
1051
- end
1052
- end
1053
-
1054
699
  xdes = space.xdes_for_page(page_number)
1055
700
  puts " Extent descriptor for pages %d-%d is at page %d, offset %d." % [
1056
701
  xdes.start_page,
@@ -1067,25 +712,23 @@ def page_account(innodb_system, space, page_number)
1067
712
 
1068
713
  xdes_status = xdes.page_status(page_number)
1069
714
  puts " Page is marked as %s in extent descriptor." % [
1070
- xdes_status[:free] ? 'free' : 'used'
715
+ xdes_status[:free] ? "free" : "used",
1071
716
  ]
1072
717
 
1073
718
  space.each_xdes_list do |name, list|
1074
- if list.include? xdes
1075
- puts " Extent is in #{name} list of space."
1076
- end
719
+ puts " Extent is in #{name} list of space." if list.include?(xdes)
1077
720
  end
1078
721
 
1079
722
  page_inode = nil
1080
723
  space.each_inode do |inode|
1081
724
  inode.each_list do |name, list|
1082
- if list.include? xdes
725
+ if list.include?(xdes)
1083
726
  page_inode = inode
1084
727
  puts " Extent is in #{name} list of fseg #{inode.fseg_id}."
1085
728
  end
1086
729
  end
1087
730
 
1088
- if inode.frag_array.include? page_number
731
+ if inode.frag_array.include?(page_number) # rubocop:disable Style/Next
1089
732
  page_inode = inode
1090
733
  puts " Page is in fragment array of fseg %d." % [
1091
734
  inode.fseg_id,
@@ -1095,65 +738,213 @@ def page_account(innodb_system, space, page_number)
1095
738
 
1096
739
  space.each_index do |index|
1097
740
  index.each_fseg do |fseg_name, fseg|
1098
- if page_inode == fseg
1099
- puts " Fseg is in #{fseg_name} fseg of index #{index.id}."
1100
- puts " Index root is page #{index.root.offset}."
1101
- if innodb_system
1102
- table_name, index_name = innodb_system.table_and_index_name_by_id(index.id)
1103
- if table_name and index_name
1104
- puts " Index is #{table_name}.#{index_name}."
1105
- end
1106
- end
741
+ next unless page_inode == fseg
742
+
743
+ puts " Fseg is in #{fseg_name} fseg of index #{index.id}."
744
+ puts " Index root is page #{index.root.offset}."
745
+ if innodb_system
746
+ table_name, index_name = innodb_system.table_and_index_name_by_id(index.id)
747
+ puts " Index is #{table_name}.#{index_name}." if table_name && index_name
1107
748
  end
1108
749
  end
1109
750
  end
1110
751
 
1111
- if space.system_space?
1112
- if page_inode == space.trx_sys.fseg
1113
- puts " Fseg is trx_sys."
1114
- end
1115
-
1116
- if page_inode == space.trx_sys.doublewrite[:fseg]
1117
- puts " Fseg is doublewrite buffer."
1118
- end
752
+ if space.system_space? # rubocop:disable Style/GuardClause
753
+ puts " Fseg is trx_sys." if page_inode == space.trx_sys.fseg
754
+ puts " Fseg is doublewrite buffer." if page_inode == space.trx_sys.doublewrite[:fseg]
1119
755
 
1120
- if innodb_system
1121
- innodb_system.data_dictionary.each_data_dictionary_index do |table_name, index_name, index|
1122
- index.each_fseg do |fseg_name, fseg|
1123
- if page_inode == fseg
1124
- puts " Index is #{table_name}.#{index_name} of data dictionary."
1125
- end
1126
- end
756
+ innodb_system.data_dictionary&.each_data_dictionary_index do |table_name, index_name, index|
757
+ index.each_fseg do |_fseg_name, fseg|
758
+ puts " Index is #{table_name}.#{index_name} of data dictionary." if page_inode == fseg
1127
759
  end
1128
760
  end
1129
761
 
1130
762
  space.trx_sys.rsegs.each_with_index do |rseg_slot, index|
1131
- if page.fil_header[:space_id] == rseg_slot[:space_id] &&
1132
- page.fil_header[:offset] == rseg_slot[:page_number]
1133
- puts " Page is a rollback segment in slot #{index}."
763
+ if page.fil_header.space_id == rseg_slot.space_id && page.fil_header.offset == rseg_slot.page_number
764
+ puts " Page is a rollback segment in slot #{index}."
1134
765
  end
1135
766
  end
1136
767
  end
1137
768
  end
1138
769
 
1139
- def page_directory_summary(space, page)
1140
- if page.type != :INDEX
1141
- usage 1, "Page must be an index page"
770
+ def page_validate_index(page)
771
+ page_is_valid = true
772
+
773
+ print "Parsing all records in page... "
774
+ records = page.each_record.to_a
775
+ puts "done."
776
+
777
+ directory_offsets = page.each_directory_offset.to_a
778
+ record_offsets = records.map(&:offset)
779
+
780
+ invalid_directory_entries = directory_offsets.reject { |n| record_offsets.include?(n) }
781
+
782
+ unless invalid_directory_entries.empty?
783
+ page_is_valid = false
784
+ puts "Invalid page directory entries (offsets not to valid records):"
785
+ invalid_directory_entries.each do |offset|
786
+ puts " slot %d, offset %d" % [
787
+ page.offset_is_directory_slot?(offset),
788
+ offset,
789
+ ]
790
+ end
791
+ end
792
+
793
+ # Read all records corresponding to valid directory entries.
794
+ directory_records = directory_offsets.reject { |o| invalid_directory_entries.include?(o) }.map { |o| page.record(o) }
795
+
796
+ misordered_directory_entries = []
797
+ prev = nil
798
+ directory_records.each do |rec|
799
+ unless prev
800
+ prev = rec
801
+ next
802
+ end
803
+ if rec.compare_key(prev.key.map { |v| v[:value] }) == 1
804
+ page_is_valid = false
805
+ misordered_directory_entries << {
806
+ slot: page.offset_is_directory_slot?(rec.offset),
807
+ offset: rec.offset,
808
+ key: rec.key_string,
809
+ prev_key: prev.key_string,
810
+ }
811
+ end
812
+ prev = rec
813
+ end
814
+ unless misordered_directory_entries.empty?
815
+ puts "Misordered page directory entries (key < prev key):"
816
+ misordered_directory_entries.each do |entry|
817
+ puts " slot %d, offset %d, key %s, prev key %s" % [
818
+ entry[:slot],
819
+ entry[:offset],
820
+ entry[:key],
821
+ entry[:prev_key],
822
+ ]
823
+ end
824
+ end
825
+
826
+ misordered_records = []
827
+ prev = nil
828
+ page.each_record do |rec|
829
+ unless prev
830
+ prev = rec
831
+ next
832
+ end
833
+ if rec.compare_key(prev.key.map { |v| v[:value] }) == 1
834
+ page_is_valid = false
835
+ misordered_records << {
836
+ offset: rec.offset,
837
+ key: rec.key_string,
838
+ prev_key: prev.key_string,
839
+ }
840
+ end
841
+ prev = rec
842
+ end
843
+ unless misordered_records.empty?
844
+ puts "Misordered records in record list (key < prev key):"
845
+ misordered_records.each do |entry|
846
+ puts " offset %d, key %s, prev key %s" % [
847
+ entry[:offset],
848
+ entry[:key],
849
+ entry[:prev_key],
850
+ ]
851
+ end
852
+ end
853
+
854
+ page_is_valid
855
+ end
856
+
857
+ def page_validate(_innodb_system, space, page_number)
858
+ page_is_valid = true
859
+ puts "Validating page %d..." % [page_number]
860
+
861
+ print "Parsing page... "
862
+ page = space.page(page_number)
863
+ puts "done."
864
+
865
+ if page.corrupt?
866
+ page_is_valid = false
867
+ puts "Page appears to be corrupt:"
868
+ puts " Stored checksums:"
869
+ puts " header %10d (0x%08x), type %s" % [
870
+ page.checksum,
871
+ page.checksum,
872
+ page.checksum_type || "unknown",
873
+ ]
874
+ puts " trailer %10d (0x%08x)" % [
875
+ page.fil_trailer.checksum,
876
+ page.fil_trailer.checksum,
877
+ ]
878
+ puts " Calculated checksums:"
879
+ puts " crc32 %10d (0x%08x)" % [
880
+ page.checksum_crc32,
881
+ page.checksum_crc32,
882
+ ]
883
+ puts " innodb %10d (0x%08x)" % [
884
+ page.checksum_innodb,
885
+ page.checksum_innodb,
886
+ ]
887
+ end
888
+
889
+ if page.torn?
890
+ page_is_valid = false
891
+ puts "Page appears to be torn:"
892
+ puts " Full LSN:"
893
+ puts " header %d (0x%016x)" % [
894
+ page.lsn,
895
+ page.lsn,
896
+ ]
897
+ puts " Low 32 bits of LSN:"
898
+ puts " header %10d (0x%08x)" % [
899
+ page.fil_header.lsn_low32,
900
+ page.fil_header.lsn_low32,
901
+ ]
902
+ puts " trailer %10d (0x%08x)" % [
903
+ page.fil_trailer.lsn_low32,
904
+ page.fil_trailer.lsn_low32,
905
+ ]
906
+ end
907
+
908
+ if page.misplaced?
909
+ page_is_valid = false
910
+ puts "Page appears to be misplaced:"
911
+ if page.misplaced_offset?
912
+ puts " Requested page %d but offset stored in page is %d." % [
913
+ page_number,
914
+ page.offset,
915
+ ]
916
+ end
917
+ if page.misplaced_space?
918
+ puts " Space ID %d does not match page stored space ID %d." % [
919
+ page.space.space_id,
920
+ page.space_id,
921
+ ]
922
+ end
1142
923
  end
1143
924
 
1144
- puts "%-8s%-8s%-14s%-8s%s" % [
1145
- "slot",
1146
- "offset",
1147
- "type",
1148
- "owned",
1149
- "key",
925
+ page_is_valid = false if page.type == :INDEX && !page_validate_index(page)
926
+
927
+ puts "Page %d appears to be %s!" % [
928
+ page_number,
929
+ page_is_valid ? "valid" : "corrupted",
930
+ ]
931
+ end
932
+
933
+ def page_directory_summary(_space, page)
934
+ usage(1, "Page must be an index page") if page.type != :INDEX
935
+
936
+ puts "%-8s%-8s%-14s%-8s%s" % %w[
937
+ slot
938
+ offset
939
+ type
940
+ owned
941
+ key
1150
942
  ]
1151
943
 
1152
944
  page.directory.each_with_index do |offset, slot|
1153
945
  record = page.record(offset)
1154
- key = if [:conventional, :node_pointer].include? record.header[:type]
1155
- "(%s)" % record.key_string
1156
- end
946
+ key = %i[conventional node_pointer].include?(record.header[:type]) ? "(%s)" % record.key_string : ""
947
+
1157
948
  puts "%-8i%-8i%-14s%-8i%s" % [
1158
949
  slot,
1159
950
  offset,
@@ -1164,57 +955,63 @@ def page_directory_summary(space, page)
1164
955
  end
1165
956
  end
1166
957
 
1167
- def page_records(space, page)
958
+ def page_records(_space, page)
1168
959
  page.each_record do |record|
1169
960
  puts "Record %i: %s" % [
1170
961
  record.offset,
1171
962
  record.string,
1172
963
  ]
1173
- puts if record.header[:type] == :conventional
1174
964
  end
1175
965
  end
1176
966
 
1177
967
  def page_illustrate(page)
1178
968
  width = 64
1179
- blocks = Array.new(page.size, " ")
969
+ unknown_page_content = page.type == :INDEX && page.record_describer.nil?
970
+ blocks = Array.new(page.size, unknown_page_content ? "▞" : " ")
1180
971
  identifiers = {}
1181
972
  identifier_sort = 0
1182
973
  count_by_identifier = Hash.new(0)
1183
974
 
1184
- page.each_region.sort { |a,b| a[:offset] <=> b[:offset]}.each do |region|
1185
- region[:length].times do |n|
975
+ page.each_region.sort_by(&:offset).each do |region|
976
+ region.length.times do |n|
1186
977
  identifier = nil
1187
978
  fraction = 0.0
1188
- if region[:name] != :garbage
1189
- if n == region[:length] - 1
1190
- fraction = 0.5
1191
- else
1192
- fraction = 1.0
1193
- end
1194
- identifier = region[:name].hash.abs
979
+ if region.name != :garbage
980
+ fraction = n == region.length - 1 ? 0.5 : 1.0
981
+ identifier = region.name.hash.abs
1195
982
  unless identifiers[identifier]
1196
983
  # Prefix an integer <0123> on each name so that the legend can be
1197
984
  # sorted by the appearance of each region in the page.
1198
985
  identifiers[identifier] = "<%04i>%s" % [
1199
986
  identifier_sort,
1200
- region[:info]
987
+ region.info,
1201
988
  ]
1202
989
  identifier_sort += 1
1203
990
  end
1204
991
  end
1205
- blocks[region[:offset] + n] = filled_block(fraction, identifier, BLOCK_CHARS_H)
992
+ blocks[region.offset + n] = filled_block(fraction, identifier, BLOCK_CHARS_H)
1206
993
  count_by_identifier[identifier] += 1
1207
994
  end
1208
995
  end
1209
996
 
1210
997
  puts
1211
- puts "%12s ╭%-#{width}s" % [ "Offset", "" * width ]
998
+ puts "%12s %-#{width}s " % ["", center("Page #{page.offset} (#{page.type})", width)]
999
+ puts "%12s ╭%-#{width}s╮" % ["Offset", "─" * width]
1212
1000
  offset = 0
1001
+ skipped_lines = 0
1213
1002
  blocks.each_slice(width) do |slice|
1214
- puts "%12i │%-s" % [offset, slice.join]
1003
+ if slice.any? { |s| s != " " }
1004
+ if skipped_lines.positive?
1005
+ puts "%12s │%-#{width}s│" % ["...", ""]
1006
+ skipped_lines = 0
1007
+ end
1008
+ puts "%12i │%-s│" % [offset, slice.join]
1009
+ else
1010
+ skipped_lines += 1
1011
+ end
1215
1012
  offset += width
1216
1013
  end
1217
- puts "%12s ╰%-#{width}s╯" % [ "", "─" * width ]
1014
+ puts "%12s ╰%-#{width}s╯" % ["", "─" * width]
1218
1015
 
1219
1016
  puts
1220
1017
  puts "Legend (%s = 1 byte):" % [filled_block(1.0, nil)]
@@ -1223,47 +1020,52 @@ def page_illustrate(page)
1223
1020
  "Bytes",
1224
1021
  "Ratio",
1225
1022
  ]
1226
- identifiers.sort { |a,b| a[1] <=> b[1] }.each do |identifier, description|
1023
+ identifiers.sort { |a, b| a[1] <=> b[1] }.each do |identifier, description|
1227
1024
  puts " %s %-30s %8i %7.2f%%" % [
1228
1025
  filled_block(1.0, identifier),
1229
1026
  description.gsub(/^<\d+>/, ""),
1230
1027
  count_by_identifier[identifier],
1231
- 100.0 * (count_by_identifier[identifier].to_f / page.size.to_f),
1028
+ 100.0 * (count_by_identifier[identifier].to_f / page.size),
1232
1029
  ]
1233
1030
  end
1234
1031
  puts " %s %-30s %8i %7.2f%%" % [
1235
1032
  filled_block(0.0, nil),
1236
1033
  "Garbage",
1237
1034
  count_by_identifier[nil],
1238
- 100.0 * (count_by_identifier[nil].to_f / page.size.to_f),
1035
+ 100.0 * (count_by_identifier[nil].to_f / page.size),
1239
1036
  ]
1240
- free_space = page.size - count_by_identifier.inject(0) { |sum,(k,v)| sum + v }
1037
+ free_space = page.size - count_by_identifier.inject(0) { |sum, (_k, v)| sum + v }
1241
1038
  puts " %s %-30s %8i %7.2f%%" % [
1242
- " ",
1243
- "Free",
1039
+ unknown_page_content ? "▞" : " ",
1040
+ unknown_page_content ? "Unknown (no data dictionary)" : "Free",
1244
1041
  free_space,
1245
- 100.0 * (free_space.to_f / page.size.to_f),
1042
+ 100.0 * (free_space.to_f / page.size),
1246
1043
  ]
1247
1044
 
1045
+ if unknown_page_content
1046
+ puts
1047
+ puts "Note:"
1048
+ puts " Records could not be parsed because no data dictionary or record describer"
1049
+ puts " was available. Use -s instead of -f, or provide a record describer class."
1050
+ end
1051
+
1248
1052
  puts
1249
1053
  end
1250
1054
 
1251
1055
  def record_dump(page, record_offset)
1252
- unless record = page.record(record_offset)
1253
- raise "Record at offset #{record_offset} not found"
1254
- end
1056
+ record = page.record(record_offset)
1057
+ raise "Record at offset #{record_offset} not found" unless record
1255
1058
 
1256
1059
  record.dump
1060
+ rescue IOError
1061
+ raise "Record could not be read at offset #{record_offset}; is it a valid record offset?"
1257
1062
  end
1258
1063
 
1259
1064
  def record_history(page, record_offset)
1260
- unless page.leaf?
1261
- raise "Record is not located on a leaf page; no history available"
1262
- end
1065
+ raise "Record is not located on a leaf page; no history available" unless page.leaf?
1263
1066
 
1264
- unless record = page.record(record_offset)
1265
- raise "Record at offset #{record_offset} not found"
1266
- end
1067
+ record = page.record(record_offset)
1068
+ raise "Record at offset #{record_offset} not found" unless record
1267
1069
 
1268
1070
  puts "%-14s%-20s%s" % [
1269
1071
  "Transaction",
@@ -1281,29 +1083,23 @@ def record_history(page, record_offset)
1281
1083
  end
1282
1084
 
1283
1085
  def index_fseg_lists(index, fseg_name)
1284
- unless index.fseg(fseg_name)
1285
- raise "File segment '#{fseg_name}' doesn't exist"
1286
- end
1086
+ raise "File segment '#{fseg_name}' doesn't exist" unless index.fseg(fseg_name)
1287
1087
 
1288
1088
  print_lists(index.each_fseg_list(index.fseg(fseg_name)))
1289
1089
  end
1290
1090
 
1291
1091
  def index_fseg_list_iterate(index, fseg_name, list_name)
1292
- unless fseg = index.fseg(fseg_name)
1293
- raise "File segment '#{fseg_name}' doesn't exist"
1294
- end
1092
+ fseg = index.fseg(fseg_name)
1093
+ raise "File segment '#{fseg_name}' doesn't exist" unless fseg
1295
1094
 
1296
- unless list = fseg.list(list_name)
1297
- raise "List '#{list_name}' doesn't exist"
1298
- end
1095
+ list = fseg.list(list_name)
1096
+ raise "List '#{list_name}' doesn't exist" unless list
1299
1097
 
1300
1098
  print_xdes_list(index.space, list)
1301
1099
  end
1302
1100
 
1303
1101
  def index_fseg_frag_pages(index, fseg_name)
1304
- unless index.fseg(fseg_name)
1305
- raise "File segment '#{fseg_name}' doesn't exist"
1306
- end
1102
+ raise "File segment '#{fseg_name}' doesn't exist" unless index.fseg(fseg_name)
1307
1103
 
1308
1104
  print_index_page_summary(index.each_fseg_frag_page(index.fseg(fseg_name)))
1309
1105
  end
@@ -1318,17 +1114,17 @@ def index_recurse(index)
1318
1114
  page.records,
1319
1115
  page.record_space,
1320
1116
  ]
1321
- if page.level == 0
1117
+ if page.level.zero?
1322
1118
  page.each_record do |record|
1323
1119
  puts "%sRECORD: (%s) → (%s)" % [
1324
- " " * (depth+1),
1120
+ " " * (depth + 1),
1325
1121
  record.key_string,
1326
1122
  record.row_string,
1327
1123
  ]
1328
1124
  end
1329
1125
  end
1330
1126
  end,
1331
- lambda do |parent_page, child_page, child_min_key, depth|
1127
+ lambda do |_parent_page, child_page, child_min_key, depth|
1332
1128
  puts "%sNODE POINTER RECORD ≥ (%s) → #%i" % [
1333
1129
  " " * depth,
1334
1130
  child_min_key.map { |r| "%s=%s" % [r[:name], r[:value].inspect] }.join(", "),
@@ -1339,13 +1135,13 @@ def index_recurse(index)
1339
1135
  end
1340
1136
 
1341
1137
  def index_record_offsets(index)
1342
- puts "%-20s%-20s" % [
1343
- "page_offset",
1344
- "record_offset",
1138
+ puts "%-20s%-20s" % %w[
1139
+ page_offset
1140
+ record_offset
1345
1141
  ]
1346
1142
  index.recurse(
1347
- lambda do |page, depth|
1348
- if page.level == 0
1143
+ lambda do |page, _depth|
1144
+ if page.level.zero?
1349
1145
  page.each_record do |record|
1350
1146
  puts "%-20i%-20i" % [
1351
1147
  page.offset,
@@ -1354,7 +1150,7 @@ def index_record_offsets(index)
1354
1150
  end
1355
1151
  end
1356
1152
  end,
1357
- lambda { |*x| }
1153
+ ->(*_) {}
1358
1154
  )
1359
1155
  end
1360
1156
 
@@ -1374,13 +1170,13 @@ def index_digraph(index)
1374
1170
  child_key.join(", "),
1375
1171
  ]
1376
1172
  end
1377
- puts " %spage_%i [ shape = \"record\"; label = \"%s\"; ];" % [
1173
+ puts " %spage_%i [ shape = 'record'; label = '%s'; ];" % [
1378
1174
  " " * depth,
1379
1175
  page.offset,
1380
1176
  label,
1381
1177
  ]
1382
1178
  end,
1383
- lambda do |parent_page, child_page, child_key, depth|
1179
+ lambda do |parent_page, child_page, _child_key, depth|
1384
1180
  puts " %spage_%i:dir_%i → page_%i:page:nw;" % [
1385
1181
  " " * depth,
1386
1182
  parent_page.offset,
@@ -1393,14 +1189,14 @@ def index_digraph(index)
1393
1189
  end
1394
1190
 
1395
1191
  def index_level_summary(index, level)
1396
- puts "%-8s%-8s%-8s%-8s%-8s%-8s%-8s" % [
1397
- "page",
1398
- "index",
1399
- "level",
1400
- "data",
1401
- "free",
1402
- "records",
1403
- "min_key",
1192
+ puts "%-8s%-8s%-8s%-8s%-8s%-8s%-8s" % %w[
1193
+ page
1194
+ index
1195
+ level
1196
+ data
1197
+ free
1198
+ records
1199
+ min_key
1404
1200
  ]
1405
1201
 
1406
1202
  index.each_page_at_level(level) do |page|
@@ -1417,15 +1213,14 @@ def index_level_summary(index, level)
1417
1213
  end
1418
1214
 
1419
1215
  def undo_history_summary(innodb_system)
1420
- history = innodb_system.history.each_history_list
1421
- history_list = history.select { |history| history.list.length > 0 }
1422
-
1423
- puts "%-8s%-8s%-14s%-20s%s" % [
1424
- "Page",
1425
- "Offset",
1426
- "Transaction",
1427
- "Type",
1428
- "Table",
1216
+ history_list = innodb_system.history.each_history_list.reject { |h| h.list.empty? }
1217
+
1218
+ puts "%-8s%-8s%-14s%-20s%s" % %w[
1219
+ Page
1220
+ Offset
1221
+ Transaction
1222
+ Type
1223
+ Table
1429
1224
  ]
1430
1225
 
1431
1226
  history_list.each do |history|
@@ -1455,20 +1250,22 @@ def usage(exit_code, message = nil)
1455
1250
  exit exit_code
1456
1251
  end
1457
1252
 
1253
+ # rubocop:disable Layout/HeredocIndentation
1458
1254
  print <<'END_OF_USAGE'
1459
1255
 
1460
1256
  Usage: innodb_space <options> <mode>
1461
1257
 
1462
1258
  Invocation examples:
1463
1259
 
1464
- innodb_space -s ibdata1 [-T tname [-I iname]] [options] <mode>
1465
- Use ibdata1 as the system tablespace and load the tname table (and the
1466
- iname index for modes that require it) from data located in the system
1467
- tablespace data dictionary. This will automatically generate a record
1468
- describer for any indexes.
1260
+ innodb_space -s ibdata1 [-T table-name [-I index-name [-R record-offset]]] [options] <mode>
1261
+ Use ibdata1 as the system tablespace and load the table-name table (and
1262
+ the index-name index for modes that require it) from data located in the
1263
+ system tablespace data dictionary. This will automatically generate a
1264
+ record describer for any indexes using the data dictionary.
1469
1265
 
1470
- innodb_space -f tname.ibd [-r ./desc.rb -d DescClass] [options] <mode>
1471
- Use the tname.ibd table (and the DescClass describer where required).
1266
+ innodb_space -f file-name.ibd [-r ./describer.rb -d DescriberClass] [options] <mode>
1267
+ Use the file-name.ibd tablespace file (and the DescriberClass describer
1268
+ where required) to read the tablespace structures or indexes.
1472
1269
 
1473
1270
  The following options are supported:
1474
1271
 
@@ -1482,16 +1279,27 @@ The following options are supported:
1482
1279
 
1483
1280
  --system-space-file, -s <arg>
1484
1281
  Load the system tablespace file or files <arg>: Either a single file e.g.
1485
- "ibdata1", a comma-delimited list of files e.g. "ibdata1,ibdata1", or a
1282
+ 'ibdata1', a comma-delimited list of files e.g. 'ibdata1,ibdata1', or a
1486
1283
  directory name. If a directory name is provided, it will be scanned for all
1487
- files named "ibdata?" which will then be sorted alphabetically and used to
1284
+ files named 'ibdata?' which will then be sorted alphabetically and used to
1488
1285
  load the system tablespace.
1489
1286
 
1490
- --table-name, -T <name>
1491
- Use the table name <name>.
1287
+ If using the --system-space-file option, the following options may also
1288
+ be used:
1289
+
1290
+ --table-name, -T <name>
1291
+ Use the table name <name>.
1292
+
1293
+ --index-name, -I <name>
1294
+ Use the index name <name>.
1492
1295
 
1493
- --index-name, -I <name>
1494
- Use the index name <name>.
1296
+ --system-space-tables, -x
1297
+ Allow opening tables from the system space to support system spaces with
1298
+ tables created without innodb-file-per-table enabled.
1299
+
1300
+ --data-directory, -D <directory>
1301
+ Open per-table tablespace files from <directory> rather than from the
1302
+ directory where the system-space-file is located.
1495
1303
 
1496
1304
  --space-file, -f <file>
1497
1305
  Load the tablespace file <file>.
@@ -1499,14 +1307,20 @@ The following options are supported:
1499
1307
  --page, -p <page>
1500
1308
  Operate on the page <page>.
1501
1309
 
1310
+ --record, -R <offset>
1311
+ Operate on the record located at <offset> within the index page.
1312
+
1502
1313
  --level, -l <level>
1503
1314
  Operate on the level <level>.
1504
1315
 
1505
1316
  --list, -L <list>
1506
1317
  Operate on the list <list>.
1507
1318
 
1319
+ --fseg-id, -F <fseg_id>
1320
+ Operate on the file segment (fseg) <fseg_id>.
1321
+
1508
1322
  --require, -r <file>
1509
- Use Ruby's "require" to load the file <file>. This is useful for loading
1323
+ Use Ruby's 'require' to load the file <file>. This is useful for loading
1510
1324
  classes with record describers.
1511
1325
 
1512
1326
  --describer, -d <describer>
@@ -1534,14 +1348,18 @@ The following modes are supported:
1534
1348
  provided with the --page/-p argument.
1535
1349
 
1536
1350
  space-index-pages-summary
1537
- Summarize all "INDEX" pages within a tablespace. This is useful to analyze
1538
- page fill rates and record counts per page. In addition to "INDEX" pages,
1539
- "ALLOCATED" pages are also printed and assumed to be completely empty.
1351
+ Summarize all 'INDEX' pages within a tablespace. This is useful to analyze
1352
+ page fill rates and record counts per page. In addition to 'INDEX' pages,
1353
+ 'ALLOCATED' pages are also printed and assumed to be completely empty.
1540
1354
  A starting page number can be provided with the --page/-p argument.
1541
1355
 
1356
+ space-index-fseg-pages-summary
1357
+ The same as space-index-pages-summary but only iterate one fseg, provided
1358
+ with the --fseg-id/-F argument.
1359
+
1542
1360
  space-index-pages-free-plot
1543
1361
  Use Ruby's gnuplot module to produce a scatterplot of page free space for
1544
- all "INDEX" and "ALLOCATED" pages in a tablespace. More aesthetically
1362
+ all 'INDEX' and 'ALLOCATED' pages in a tablespace. More aesthetically
1545
1363
  pleasing plots can be produced with space-index-pages-summary output,
1546
1364
  but this is a quick and easy way to produce a passable plot. A starting
1547
1365
  page number can be provided with the --page/-p argument.
@@ -1573,19 +1391,13 @@ The following modes are supported:
1573
1391
  color and Unicode box drawing characters to show page usage throughout
1574
1392
  the space.
1575
1393
 
1576
- space-extents-illustrate-svg
1577
- Iterate through all extents, illustrating the extent usage in SVG format
1578
- printed to stdout to show page usage throughout the space.
1579
-
1580
1394
  space-lsn-age-illustrate
1581
1395
  Iterate through all pages, producing a heat map colored by the page LSN
1582
1396
  using ANSI color and Unicode box drawing characters, allowing the user to
1583
1397
  get an overview of page modification recency.
1584
1398
 
1585
- space-lsn-age-illustrate-svg
1586
- Iterate through all pages, producing a heat map colored by the page LSN
1587
- producing SVG format output, allowing the user to get an overview of page
1588
- modification recency.
1399
+ space-inodes-fseg-id
1400
+ Iterate through all inodes, printing only the FSEG ID.
1589
1401
 
1590
1402
  space-inodes-summary
1591
1403
  Iterate through all inodes, printing a short summary of each FSEG.
@@ -1621,7 +1433,7 @@ The following modes are supported:
1621
1433
  Iterate the file segment list (whose name is provided in the first --list/-L
1622
1434
  argument) for internal or leaf pages for a given index (whose root page
1623
1435
  is provided in the first --page/-p argument). The lists used for each
1624
- index are "full", "not_full", and "free".
1436
+ index are 'full', 'not_full', and 'free'.
1625
1437
 
1626
1438
  index-fseg-internal-frag-pages
1627
1439
  index-fseg-leaf-frag-pages
@@ -1629,11 +1441,14 @@ The following modes are supported:
1629
1441
  page must be provided with --page/-p.
1630
1442
 
1631
1443
  page-dump
1632
- Dump the contents of a page, using the Ruby pp ("pretty-print") module.
1444
+ Dump the contents of a page, using the Ruby pp ('pretty-print') module.
1633
1445
 
1634
1446
  page-account
1635
1447
  Account for a page's usage in FSEGs.
1636
1448
 
1449
+ page-validate
1450
+ Validate the contents of a page.
1451
+
1637
1452
  page-directory-summary
1638
1453
  Summarize the record contents of the page directory in a page. If a record
1639
1454
  describer is available, the key of each record will be printed.
@@ -1660,16 +1475,20 @@ The following modes are supported:
1660
1475
  A record offset must be provided with -R/--record.
1661
1476
 
1662
1477
  END_OF_USAGE
1478
+ # rubocop:enable Layout/HeredocIndentation
1663
1479
 
1664
1480
  exit exit_code
1665
1481
  end
1666
1482
 
1667
- Signal.trap("INT") { exit }
1668
- Signal.trap("PIPE") { exit }
1483
+ %w[INT PIPE].each do |name|
1484
+ Signal.trap(name) { exit } if Signal.list.include?(name)
1485
+ end
1669
1486
 
1670
1487
  @options = OpenStruct.new
1671
1488
  @options.trace = 0
1672
1489
  @options.system_space_file = nil
1490
+ @options.system_space_tables = false
1491
+ @options.data_directory = nil
1673
1492
  @options.space_file = nil
1674
1493
  @options.table_name = nil
1675
1494
  @options.index_name = nil
@@ -1677,14 +1496,18 @@ Signal.trap("PIPE") { exit }
1677
1496
  @options.record = nil
1678
1497
  @options.level = nil
1679
1498
  @options.list = nil
1499
+ @options.fseg_id = nil
1680
1500
  @options.describer = nil
1681
1501
  @options.illustration_line_width = 64
1682
1502
  @options.illustration_block_size = 8
1683
1503
 
1504
+ # rubocop:disable Layout/SpaceInsideArrayLiteralBrackets
1684
1505
  getopt_options = [
1685
1506
  [ "--help", "-?", GetoptLong::NO_ARGUMENT ],
1686
1507
  [ "--trace", "-t", GetoptLong::NO_ARGUMENT ],
1687
1508
  [ "--system-space-file", "-s", GetoptLong::REQUIRED_ARGUMENT ],
1509
+ [ "--system-space-tables", "-x", GetoptLong::NO_ARGUMENT ],
1510
+ [ "--data-directory", "-D", GetoptLong::REQUIRED_ARGUMENT ],
1688
1511
  [ "--space-file", "-f", GetoptLong::REQUIRED_ARGUMENT ],
1689
1512
  [ "--table-name", "-T", GetoptLong::REQUIRED_ARGUMENT ],
1690
1513
  [ "--index-name", "-I", GetoptLong::REQUIRED_ARGUMENT ],
@@ -1692,11 +1515,13 @@ getopt_options = [
1692
1515
  [ "--record", "-R", GetoptLong::REQUIRED_ARGUMENT ],
1693
1516
  [ "--level", "-l", GetoptLong::REQUIRED_ARGUMENT ],
1694
1517
  [ "--list", "-L", GetoptLong::REQUIRED_ARGUMENT ],
1518
+ [ "--fseg-id", "-F", GetoptLong::REQUIRED_ARGUMENT ],
1695
1519
  [ "--require", "-r", GetoptLong::REQUIRED_ARGUMENT ],
1696
1520
  [ "--describer", "-d", GetoptLong::REQUIRED_ARGUMENT ],
1697
1521
  [ "--illustration-line-width", GetoptLong::REQUIRED_ARGUMENT ],
1698
1522
  [ "--illustration-block-size", GetoptLong::REQUIRED_ARGUMENT ],
1699
1523
  ]
1524
+ # rubocop:enable Layout/SpaceInsideArrayLiteralBrackets
1700
1525
 
1701
1526
  getopt = GetoptLong.new(*getopt_options)
1702
1527
 
@@ -1708,6 +1533,10 @@ getopt.each do |opt, arg|
1708
1533
  @options.trace += 1
1709
1534
  when "--system-space-file"
1710
1535
  @options.system_space_file = arg.split(",")
1536
+ when "--system-space-tables"
1537
+ @options.system_space_tables = true
1538
+ when "--data-directory"
1539
+ @options.data_directory = arg
1711
1540
  when "--space-file"
1712
1541
  @options.space_file = arg.split(",")
1713
1542
  when "--table-name"
@@ -1722,6 +1551,8 @@ getopt.each do |opt, arg|
1722
1551
  @options.level = arg.to_i
1723
1552
  when "--list"
1724
1553
  @options.list = arg.to_sym
1554
+ when "--fseg-id"
1555
+ @options.fseg_id = arg.to_i
1725
1556
  when "--require"
1726
1557
  require File.expand_path(arg)
1727
1558
  when "--describer"
@@ -1733,18 +1564,31 @@ getopt.each do |opt, arg|
1733
1564
  end
1734
1565
  end
1735
1566
 
1736
- unless @options.system_space_file or @options.space_file
1737
- usage 1, "System space file (-s) or space file (-f) must be specified"
1567
+ # rubocop:disable Style/IfUnlessModifier
1568
+
1569
+ unless @options.system_space_file || @options.space_file
1570
+ usage(1, "Either the --system-space-file (-s) or --space-file (-f) must be specified")
1738
1571
  end
1739
1572
 
1740
- if @options.system_space_file and @options.space_file
1741
- usage 1, "Only one of system space or space file may be specified"
1573
+ if @options.system_space_file && @options.space_file
1574
+ usage(1, "Only one of --system-space-file (-s) or --space-file (-f) may be specified")
1742
1575
  end
1743
1576
 
1744
- if @options.trace > 1
1745
- BufferCursor.trace!
1577
+ system_space_options =
1578
+ @options.table_name || @options.index_name || @options.system_space_tables || @options.data_directory
1579
+
1580
+ if !@options.system_space_file && system_space_options
1581
+ usage(
1582
+ 1,
1583
+ %{
1584
+ The --table-name (-T), --index-name (-I), --system-space-tables (-x), and --data-directory (-D)
1585
+ options can only be used with the --system-space-file (-s) option
1586
+ }.squish
1587
+ )
1746
1588
  end
1747
1589
 
1590
+ BufferCursor.trace! if @options.trace > 1
1591
+
1748
1592
  # A few globals that we'll try to populate from the command-line arguments.
1749
1593
  innodb_system = nil
1750
1594
  space = nil
@@ -1752,12 +1596,18 @@ index = nil
1752
1596
  page = nil
1753
1597
 
1754
1598
  if @options.system_space_file
1755
- innodb_system = Innodb::System.new(@options.system_space_file)
1599
+ innodb_system = Innodb::System.new(@options.system_space_file, data_directory: @options.data_directory)
1756
1600
  end
1757
1601
 
1758
- if innodb_system and @options.table_name
1602
+ if innodb_system && @options.table_name
1759
1603
  table_tablespace = innodb_system.space_by_table_name(@options.table_name)
1760
- space = table_tablespace || innodb_system.system_space
1604
+ if table_tablespace
1605
+ space = table_tablespace
1606
+ elsif @options.system_space_tables
1607
+ space = innodb_system.system_space
1608
+ else
1609
+ raise "Tablespace file not found and --system-space-tables (-x) is not enabled"
1610
+ end
1761
1611
  elsif @options.space_file
1762
1612
  space = Innodb::Space.new(@options.space_file)
1763
1613
  else
@@ -1765,24 +1615,17 @@ else
1765
1615
  end
1766
1616
 
1767
1617
  if @options.describer
1768
- describer = eval(@options.describer)
1769
- unless describer
1770
- describer = Innodb::RecordDescriber.const_get(@options.describer)
1771
- end
1618
+ describer = eval(@options.describer) # rubocop:disable Security/Eval
1619
+ describer ||= Innodb::RecordDescriber.const_get(@options.describer)
1772
1620
  space.record_describer = describer.new
1773
1621
  end
1774
1622
 
1775
- if innodb_system and @options.table_name and @options.index_name
1623
+ if innodb_system && @options.table_name && @options.index_name
1776
1624
  index = innodb_system.index_by_name(@options.table_name, @options.index_name)
1777
- if @options.page
1778
- page = space.page(@options.page)
1779
- else
1780
- page = index.root
1781
- end
1625
+ page = @options.page ? space.page(@options.page) : index.root
1782
1626
  elsif @options.page
1783
- if page = space.page(@options.page) and page.type == :INDEX and page.root?
1784
- index = space.index(@options.page)
1785
- end
1627
+ page = space.page(@options.page)
1628
+ index = space.index(@options.page) if page&.type == :INDEX && page&.root?
1786
1629
  end
1787
1630
 
1788
1631
  # The non-option argument on the command line is the mode (usually the last,
@@ -1790,50 +1633,82 @@ end
1790
1633
  mode = ARGV.shift
1791
1634
 
1792
1635
  unless mode
1793
- usage 1, "At least one mode must be provided"
1636
+ usage(1, "At least one mode must be provided")
1794
1637
  end
1795
1638
 
1796
- if /^(system-|data-dictionary-)/.match(mode) and !innodb_system
1797
- usage 1, "System tablespace must be specified using -s/--system-space-file"
1639
+ if /^(system-|data-dictionary-)/.match(mode) && !innodb_system
1640
+ usage(1, "System tablespace must be specified using --system-space-file (-s)")
1798
1641
  end
1799
1642
 
1800
- if /^space-/.match(mode) and !space
1801
- usage 1, "Tablespace must be specified using either -f/--space-file or a combination of -s/--system-space-file and -T/--table"
1643
+ if /^space-/.match(mode) && !space
1644
+ usage(
1645
+ 1,
1646
+ %{
1647
+ Tablespace must be specified using either --space-file (-f)
1648
+ or a combination of --system-space-file (-s) and --table (-T)
1649
+ }.squish
1650
+ )
1802
1651
  end
1803
1652
 
1804
- if /^index-/.match(mode) and !index
1805
- usage 1, "Index must be specified using a combination of either -f/--space-file and -p/--page or -s/--system-space-file, -T/--table-name, and -I/--index-name"
1653
+ if /^index-/.match(mode) && !index
1654
+ usage(
1655
+ 1,
1656
+ %{
1657
+ Index must be specified using a combination of either --space-file (-f) and --page (-p)
1658
+ or --system-space-file (-s), --table-name (-T), and --index-name (-I)
1659
+ }.squish
1660
+ )
1806
1661
  end
1807
1662
 
1808
- if /^page-/.match(mode) and !page
1809
- usage 1, "Page number must be specified using -p/--page"
1663
+ if /^page-/.match(mode) && !page
1664
+ usage(1, "Page number must be specified using --page (-p)")
1810
1665
  end
1811
1666
 
1812
- if /^record-/.match(mode) and !@options.record
1813
- usage 1, "Record offset must be specified using -R/--record"
1667
+ if /^record-/.match(mode) && !@options.record
1668
+ usage(1, "Record offset must be specified using --record (-R)")
1814
1669
  end
1815
1670
 
1816
- if /-list-iterate$/.match(mode) and !@options.list
1817
- usage 1, "List name must be specified using -L/--list"
1671
+ if /^record-/.match(mode) && !page
1672
+ usage(
1673
+ 1,
1674
+ %{
1675
+ An index page must be available when using --record (-R); specify either
1676
+ --page (-p) or --table-name (-T) and --index-name (-I) for the index root page.
1677
+ }
1678
+ )
1818
1679
  end
1819
1680
 
1820
- if /-level-/.match(mode) and !@options.level
1821
- usage 1, "Level must be specified using -l/--level"
1681
+ if /^record-/.match(mode) && page.type != :INDEX
1682
+ usage(1, "Mode #{mode} may be used only with index pages")
1822
1683
  end
1823
1684
 
1824
- if [
1825
- "index-recurse",
1826
- "index-record-offsets",
1827
- "index-digraph",
1828
- "index-level-summary",
1829
- ].include?(mode) and !index.record_describer
1830
- usage 1, "Record describer must be specified using -d/--describer"
1685
+ if /-list-iterate$/.match(mode) && !@options.list
1686
+ usage(1, "List name must be specified using --list (-L)")
1831
1687
  end
1832
1688
 
1833
- if @options.trace > 0
1834
- BufferCursor.trace!
1689
+ if /-level-/.match(mode) && !@options.level
1690
+ usage(1, "Level must be specified using --level (-l)")
1835
1691
  end
1836
1692
 
1693
+ if %w[
1694
+ index-recurse
1695
+ index-record-offsets
1696
+ index-digraph
1697
+ index-level-summary
1698
+ ].include?(mode) && !index.record_describer
1699
+ usage(1, "Record describer must be specified using --describer (-d)")
1700
+ end
1701
+
1702
+ if %w[
1703
+ space-index-fseg-pages-summary
1704
+ ].include?(mode) && !@options.fseg_id
1705
+ usage(1, "File segment id must be specified using --fseg-id (-F)")
1706
+ end
1707
+
1708
+ # rubocop:enable Style/IfUnlessModifier
1709
+
1710
+ BufferCursor.trace! if @options.trace.positive?
1711
+
1837
1712
  case mode
1838
1713
  when "system-spaces"
1839
1714
  system_spaces(innodb_system)
@@ -1849,9 +1724,10 @@ when "space-summary"
1849
1724
  space_summary(space, @options.page || 0)
1850
1725
  when "space-index-pages-summary"
1851
1726
  space_index_pages_summary(space, @options.page || 0)
1727
+ when "space-index-fseg-pages-summary"
1728
+ space_index_fseg_pages_summary(space, @options.fseg_id)
1852
1729
  when "space-index-pages-free-plot"
1853
- file_name = space.name.sub(".ibd", "").sub(/[^a-zA-Z0-9_]/, "_")
1854
- space_index_pages_free_plot(space, file_name, @options.page || 0)
1730
+ space_index_pages_free_plot(space, @options.page || 0)
1855
1731
  when "space-page-type-regions"
1856
1732
  space_page_type_regions(space, @options.page || 0)
1857
1733
  when "space-page-type-summary"
@@ -1866,12 +1742,10 @@ when "space-extents"
1866
1742
  space_extents(space)
1867
1743
  when "space-extents-illustrate"
1868
1744
  space_extents_illustrate(space)
1869
- when "space-extents-illustrate-svg"
1870
- space_extents_illustrate_svg(space)
1871
1745
  when "space-lsn-age-illustrate"
1872
1746
  space_lsn_age_illustrate(space)
1873
- when "space-lsn-age-illustrate-svg"
1874
- space_lsn_age_illustrate_svg(space)
1747
+ when "space-inodes-fseg-id"
1748
+ space_inodes_fseg_id(space)
1875
1749
  when "space-inodes-summary"
1876
1750
  space_inodes_summary(space)
1877
1751
  when "space-inodes-detail"
@@ -1900,6 +1774,8 @@ when "page-dump"
1900
1774
  page.dump
1901
1775
  when "page-account"
1902
1776
  page_account(innodb_system, space, @options.page)
1777
+ when "page-validate"
1778
+ page_validate(innodb_system, space, @options.page)
1903
1779
  when "page-directory-summary"
1904
1780
  page_directory_summary(space, page)
1905
1781
  when "page-records"