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