innodb_ruby 0.7.11 → 0.7.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -36,7 +36,7 @@ def print_xdes_list(list)
36
36
  list.each do |entry|
37
37
  puts "%-12i%-64s" % [
38
38
  entry.xdes[:start_page],
39
- entry.each_page_status.inject("") { |bitmap, page_status|
39
+ entry.each_page_status.inject("") { |bitmap, (page_number, page_status)|
40
40
  bitmap += page_status[:free] ? "." : "#"
41
41
  bitmap
42
42
  },
@@ -151,29 +151,13 @@ def space_indexes(space)
151
151
 
152
152
  space.each_index do |index|
153
153
  index.each_fseg do |fseg_name, fseg|
154
- fragments =
155
- fseg[:frag_array].inject(0) { |c, i| c += 1 if i; c }
156
-
157
- used =
158
- fragments +
159
- fseg[:not_full_n_used] +
160
- space.pages_per_extent * fseg[:full].base[:length]
161
-
162
- allocated =
163
- fragments +
164
- space.pages_per_extent * fseg[:full].base[:length] +
165
- space.pages_per_extent * fseg[:not_full].base[:length] +
166
- space.pages_per_extent * fseg[:free].base[:length]
167
-
168
- fill_factor = allocated > 0 ? 100.0 * (used.to_f / allocated.to_f) : 0.0
169
-
170
154
  puts "%-12i%-12i%-12s%-12i%-12i%-12s" % [
171
155
  index.id,
172
156
  index.root.offset,
173
157
  fseg_name,
174
- used,
175
- allocated,
176
- "%.2f%%" % fill_factor,
158
+ fseg.used_pages,
159
+ fseg.total_pages,
160
+ "%.2f%%" % fseg.fill_factor,
177
161
  ]
178
162
  end
179
163
  end
@@ -229,6 +213,134 @@ def space_extents(space)
229
213
  print_xdes_list(space.each_xdes)
230
214
  end
231
215
 
216
+ def print_inode_summary(inode)
217
+ puts "INODE fseg_id=%d, pages=%d, frag=%d, full=%d, not_full=%d, free=%d" % [
218
+ inode.fseg_id,
219
+ inode.total_pages,
220
+ inode.frag_array_n_used,
221
+ inode.full.length,
222
+ inode.not_full.length,
223
+ inode.free.length,
224
+ ]
225
+ end
226
+
227
+ def print_inode_detail(inode)
228
+ 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)" % [
229
+ inode.fseg_id,
230
+ inode.total_pages,
231
+ inode.frag_array_n_used,
232
+ inode.frag_array_pages.join(", "),
233
+ inode.full.length,
234
+ inode.full.each.to_a.map { |x| "#{x.start_page}-#{x.end_page}" }.join(", "),
235
+ inode.not_full.length,
236
+ inode.not_full.each.to_a.map { |x| "#{x.start_page}-#{x.end_page}" }.join(", "),
237
+ inode.not_full_n_used,
238
+ inode.not_full.length * inode.space.pages_per_extent,
239
+ inode.free.length,
240
+ inode.free.each.to_a.map { |x| "#{x.start_page}-#{x.end_page}" }.join(", "),
241
+ ]
242
+ end
243
+
244
+ def space_inodes_summary(space)
245
+ space.each_inode do |inode|
246
+ print_inode_summary(inode)
247
+ end
248
+ end
249
+
250
+ def space_inodes_detail(space)
251
+ space.each_inode do |inode|
252
+ print_inode_detail(inode)
253
+ end
254
+ end
255
+
256
+ def page_account(space, page_number)
257
+ puts "Accounting for page #{page_number}:"
258
+
259
+ if page_number > space.pages
260
+ puts " Page does not exist."
261
+ return
262
+ end
263
+
264
+ page = space.page(page_number)
265
+ puts " Page type is #{page.type}."
266
+
267
+ xdes = space.xdes_for_page(page_number)
268
+ puts " Extent descriptor for pages %d-%d is at page %d, offset %d." % [
269
+ xdes.start_page,
270
+ xdes.end_page,
271
+ xdes.this[:page],
272
+ xdes.this[:offset],
273
+ ]
274
+
275
+ if xdes.allocated_to_fseg?
276
+ puts " Extent is fully allocated to fseg #{xdes.fseg_id}."
277
+ else
278
+ puts " Extent is not fully allocated to an fseg; may be a fragment extent."
279
+ end
280
+
281
+ xdes_status = xdes.page_status(page_number)
282
+ puts " Page is marked as %s in extent descriptor." % [
283
+ xdes_status[:free] ? 'free' : 'used'
284
+ ]
285
+
286
+ space.each_xdes_list do |name, list|
287
+ if list.include? xdes
288
+ puts " Extent is in #{name} list of space."
289
+ end
290
+ end
291
+
292
+ page_inode = nil
293
+ space.each_inode do |inode|
294
+ inode.each_list do |name, list|
295
+ if list.include? xdes
296
+ page_inode = inode
297
+ puts " Extent is in #{name} list of fseg #{inode.fseg_id}."
298
+ end
299
+ end
300
+
301
+ if inode.frag_array.include? page_number
302
+ page_inode = inode
303
+ puts " Page is in fragment array of fseg %d." % [
304
+ inode.fseg_id,
305
+ ]
306
+ end
307
+ end
308
+
309
+ space.each_index do |index|
310
+ index.each_fseg do |fseg_name, fseg|
311
+ if page_inode == fseg
312
+ puts " Fseg is in #{fseg_name} fseg of index #{index.id}."
313
+ puts " Index root is page #{index.root.offset}."
314
+ end
315
+ end
316
+ end
317
+
318
+ if space.system_space?
319
+ if page_inode == space.trx_sys.fseg
320
+ puts " Fseg is trx_sys."
321
+ end
322
+
323
+ if page_inode == space.trx_sys.doublewrite[:fseg]
324
+ puts " Fseg is doublewrite buffer."
325
+ end
326
+
327
+ space.data_dictionary.each_index do |table_name, index_name, index|
328
+ index.each_fseg do |fseg_name, fseg|
329
+ if page_inode == fseg
330
+ puts " Index is #{table_name}.#{index_name} of data dictionary."
331
+ end
332
+ end
333
+ end
334
+
335
+ space.trx_sys.rsegs.each_with_index do |rseg_slot, index|
336
+ if page.fil_header[:space_id] == rseg_slot[:space_id] &&
337
+ page.fil_header[:offset] == rseg_slot[:page_number]
338
+ puts " Page is a rollback segment in slot #{index}."
339
+ end
340
+ end
341
+ end
342
+ end
343
+
232
344
  def page_directory_summary(page)
233
345
  puts "%-8s%-8s%-14s%-8s%s" % [
234
346
  "slot",
@@ -264,16 +376,15 @@ def index_fseg_lists(index, fseg_name)
264
376
  end
265
377
 
266
378
  def index_fseg_list_iterate(index, fseg_name, list_name)
267
- unless index.fseg(fseg_name)
379
+ unless fseg = index.fseg(fseg_name)
268
380
  raise "File segment '#{fseg_name}' doesn't exist"
269
381
  end
270
382
 
271
- fseg = index.fseg(fseg_name)
272
- unless fseg[list_name] && fseg[list_name].is_a?(Innodb::List)
383
+ unless list = fseg.list(list_name)
273
384
  raise "List '#{list_name}' doesn't exist"
274
385
  end
275
386
 
276
- print_xdes_list(fseg[list_name])
387
+ print_xdes_list(list)
277
388
  end
278
389
 
279
390
  def index_fseg_frag_pages(index, fseg_name)
@@ -444,9 +555,18 @@ The following modes are supported:
444
555
  space-extents
445
556
  Iterate through all extents, printing the extent descriptor bitmap.
446
557
 
558
+ space-inodes-summary
559
+ Iterate through all inodes, printing a short summary of each FSEG.
560
+
561
+ space-inodes-detail
562
+ Iterate through all inodes, printing a detailed report of each FSEG.
563
+
447
564
  page-dump
448
565
  Dump the contents of a page, using the Ruby pp ("pretty-print") module.
449
566
 
567
+ page-account
568
+ Account for a page's usage in FSEGs.
569
+
450
570
  page-directory-summary
451
571
  Summarize the record contents of the page directory in a page. If a record
452
572
  describer is available, the key of each record will be printed.
@@ -500,6 +620,7 @@ Signal.trap("PIPE") { exit }
500
620
 
501
621
  getopt_options = [
502
622
  [ "--help", "-?", GetoptLong::NO_ARGUMENT ],
623
+ [ "--trace", "-t", GetoptLong::NO_ARGUMENT ],
503
624
  [ "--file", "-f", GetoptLong::REQUIRED_ARGUMENT ],
504
625
  [ "--page-size", "-P", GetoptLong::REQUIRED_ARGUMENT ],
505
626
  [ "--page", "-p", GetoptLong::REQUIRED_ARGUMENT ],
@@ -515,6 +636,8 @@ getopt.each do |opt, arg|
515
636
  case opt
516
637
  when "--help"
517
638
  usage 0
639
+ when "--trace"
640
+ Innodb::Cursor.trace!
518
641
  when "--mode"
519
642
  @options.mode = arg
520
643
  when "--file"
@@ -544,7 +667,9 @@ end
544
667
  space = Innodb::Space.new(@options.file, @options.page_size)
545
668
 
546
669
  if @options.describer
547
- space.record_describer = Innodb::RecordDescriber.const_get(@options.describer)
670
+ unless space.record_describer = eval(@options.describer)
671
+ space.record_describer = Innodb::RecordDescriber.const_get(@options.describer)
672
+ end
548
673
  end
549
674
 
550
675
  if ARGV.empty?
@@ -561,6 +686,14 @@ ARGV.each do |mode|
561
686
  @options.pages.each do |page_number|
562
687
  space.page(page_number).dump
563
688
  end
689
+ when "page-account"
690
+ if @options.pages.empty?
691
+ usage 1, "Page numbers to dump must be provided with --page/-p"
692
+ end
693
+
694
+ @options.pages.each do |page_number|
695
+ page_account(space, page_number)
696
+ end
564
697
  when "page-directory-summary"
565
698
  if @options.pages.empty?
566
699
  usage 1, "Page numbers to dump must be provided with --page/-p"
@@ -595,6 +728,10 @@ ARGV.each do |mode|
595
728
  space_indexes(space)
596
729
  when "space-extents"
597
730
  space_extents(space)
731
+ when "space-inodes-summary"
732
+ space_inodes_summary(space)
733
+ when "space-inodes-detail"
734
+ space_inodes_detail(space)
598
735
  when "index-recurse"
599
736
  unless space.record_describer
600
737
  usage 1, "Record describer necessary for index recursion"
@@ -1,18 +1,25 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  # A set of classes for parsing and working with InnoDB data files.
2
3
  module Innodb; end
3
4
 
4
5
  require "enumerator"
5
6
 
6
7
  require "innodb/version"
8
+ require "innodb/checksum"
7
9
  require "innodb/page"
10
+ require "innodb/page/blob"
8
11
  require "innodb/page/fsp_hdr_xdes"
9
12
  require "innodb/page/inode"
10
13
  require "innodb/page/index"
11
14
  require "innodb/page/trx_sys"
15
+ require "innodb/page/sys"
16
+ require "innodb/page/undo_log"
12
17
  require "innodb/record_describer"
13
18
  require "innodb/field"
14
19
  require "innodb/space"
20
+ require "innodb/inode"
15
21
  require "innodb/index"
16
22
  require "innodb/log_block"
17
23
  require "innodb/log"
18
- require "innodb/xdes"
24
+ require "innodb/undo_log"
25
+ require "innodb/xdes"
@@ -0,0 +1,30 @@
1
+ # -*- encoding : utf-8 -*-
2
+ class Innodb::Checksum
3
+ MAX = 0xFFFFFFFF.freeze
4
+ MASK1 = 1463735687.freeze
5
+ MASK2 = 1653893711.freeze
6
+
7
+ # This is derived from ut_fold_ulint_pair in include/ut0rnd.ic in the
8
+ # InnoDB source code. Since Ruby's Bignum class is *much* slower than its
9
+ # Fixnum class, we mask back to 32 bits to keep things from overflowing
10
+ # and being promoted to Bignum.
11
+ def self.fold_pair(n1, n2)
12
+ (((((((n1 ^ n2 ^ MASK2) << 8) & MAX) + n1) & MAX) ^ MASK1) + n2) & MAX
13
+ end
14
+
15
+ # Iterate through the provided enumerator, which is expected to return a
16
+ # Fixnum (or something coercible to it), and "fold" them together to produce
17
+ # a single value.
18
+ def self.fold_enumerator(enumerator)
19
+ fold = 0
20
+ enumerator.each do |byte|
21
+ fold = fold_pair(fold, byte)
22
+ end
23
+ fold
24
+ end
25
+
26
+ # A simple helper (and example) to fold a provided string.
27
+ def self.fold_string(string)
28
+ fold_enumerator(string.bytes)
29
+ end
30
+ end
@@ -1,62 +1,156 @@
1
+ # -*- encoding : utf-8 -*-
1
2
  require "bindata"
2
3
 
3
4
  # A cursor to walk through InnoDB data structures to read fields.
4
5
  class Innodb::Cursor
5
- def initialize(buffer, offset)
6
+
7
+ # An entry in a stack of cursors. The cursor position, direction, and
8
+ # name array are each attributes of the current cursor stack and are
9
+ # manipulated together.
10
+ class StackEntry
11
+ attr_accessor :cursor
12
+ attr_accessor :position
13
+ attr_accessor :direction
14
+ attr_accessor :name
15
+
16
+ def initialize(cursor, position=0, direction=:forward, name=nil)
17
+ @cursor = cursor
18
+ @position = position
19
+ @direction = direction
20
+ @name = name || []
21
+ end
22
+
23
+ def dup
24
+ StackEntry.new(cursor, position, direction, name.dup)
25
+ end
26
+ end
27
+
28
+ @@tracing = false
29
+
30
+ # Enable tracing for all Innodb::Cursor objects.
31
+ def self.trace!(arg=true)
32
+ @@tracing = arg
33
+ end
34
+
35
+ # Initialize a cursor within a buffer at the given position.
36
+ def initialize(buffer, position)
6
37
  @buffer = buffer
7
- @cursor = [ offset ]
8
- @direction = :forward
38
+ @stack = [ StackEntry.new(self, position) ]
39
+
40
+ trace_with :print_trace
41
+ end
42
+
43
+ # Print a trace output for this cursor. The method is passed a cursor object,
44
+ # position, raw byte buffer, and array of names.
45
+ def print_trace(cursor, position, bytes, name)
46
+ slice_size = 16
47
+ bytes.each_slice(slice_size).each_with_index do |slice_bytes, slice_count|
48
+ puts "%06i %s %-32s %s" % [
49
+ position + (slice_count * slice_size),
50
+ direction == :backward ? "←" : "→",
51
+ slice_bytes.map { |n| "%02x" % n }.join,
52
+ slice_count == 0 ? name.join(".") : "↵",
53
+ ]
54
+ end
55
+ end
56
+
57
+ # Set a Proc or method on self to trace with.
58
+ def trace_with(arg=nil)
59
+ if arg.nil?
60
+ @trace_proc = nil
61
+ elsif arg.class == Proc
62
+ @trace_proc = arg
63
+ elsif arg.class == Symbol
64
+ @trace_proc = lambda { |cursor, position, bytes, name| self.send(arg, cursor, position, bytes, name) }
65
+ else
66
+ raise "Don't know how to trace with #{arg}"
67
+ end
68
+ end
69
+
70
+ # Generate a trace record from the current cursor.
71
+ def trace(position, bytes, name)
72
+ @trace_proc.call(self, position, bytes, name) if @@tracing && @trace_proc
73
+ end
74
+
75
+ # The current cursor object; the top of the stack.
76
+ def current
77
+ @stack.last
78
+ end
79
+
80
+ # Set the field name.
81
+ def name(name_arg=nil)
82
+ if name_arg.nil?
83
+ return current.name
84
+ end
85
+
86
+ unless block_given?
87
+ raise "No block given"
88
+ end
89
+
90
+ current.name.push name_arg
91
+ ret = yield(self)
92
+ current.name.pop
93
+ ret
94
+ end
95
+
96
+ # Return the direction of the current cursor.
97
+ def direction(direction_arg=nil)
98
+ if direction_arg.nil?
99
+ return current.direction
100
+ end
101
+
102
+ current.direction = direction_arg
103
+ self
9
104
  end
10
105
 
11
106
  # Set the direction of the cursor to "forward".
12
107
  def forward
13
- @direction = :forward
14
- self
108
+ direction(:forward)
15
109
  end
16
110
 
17
111
  # Set the direction of the cursor to "backward".
18
112
  def backward
19
- @direction = :backward
20
- self
113
+ direction(:backward)
21
114
  end
22
115
 
23
116
  # Return the position of the current cursor.
24
117
  def position
25
- @cursor[0]
118
+ current.position
26
119
  end
27
120
 
28
121
  # Move the current cursor to a new absolute position.
29
- def seek(offset)
30
- @cursor[0] = offset if offset
122
+ def seek(position)
123
+ current.position = position if position
31
124
  self
32
125
  end
33
126
 
34
127
  # Adjust the current cursor to a new relative position.
35
- def adjust(relative_offset)
36
- @cursor[0] += relative_offset
128
+ def adjust(relative_position)
129
+ current.position += relative_position
37
130
  self
38
131
  end
39
132
 
40
133
  # Save the current cursor position and start a new (nested, stacked) cursor.
41
- def push(offset=nil)
42
- @cursor.unshift(offset.nil? ? @cursor[0] : offset)
134
+ def push(position=nil)
135
+ @stack.push current.dup
136
+ seek(position)
43
137
  self
44
138
  end
45
139
 
46
140
  # Restore the last cursor position.
47
141
  def pop
48
- raise "No cursors to pop" unless @cursor.size > 1
49
- @cursor.shift
142
+ raise "No cursors to pop" unless @stack.size > 1
143
+ @stack.pop
50
144
  self
51
145
  end
52
146
 
53
147
  # Execute a block and restore the cursor to the previous position after
54
148
  # the block returns. Return the block's return value after restoring the
55
- # cursor.
56
- def peek
149
+ # cursor. Optionally seek to provided position before executing block.
150
+ def peek(position=nil)
57
151
  raise "No block given" unless block_given?
58
- push
59
- result = yield
152
+ push(position)
153
+ result = yield(self)
60
154
  pop
61
155
  result
62
156
  end
@@ -65,16 +159,17 @@ class Innodb::Cursor
65
159
  # position and adjust the cursor position by that amount.
66
160
  def read_and_advance(length)
67
161
  data = nil
68
- #print "data(#{@cursor[0]}..."
69
- case @direction
162
+ cursor_start = current.position
163
+ case current.direction
70
164
  when :forward
71
- data = @buffer.data(@cursor[0], length)
165
+ data = @buffer.data(current.position, length)
72
166
  adjust(length)
73
167
  when :backward
74
168
  adjust(-length)
75
- data = @buffer.data(@cursor[0], length)
169
+ data = @buffer.data(current.position, length)
76
170
  end
77
- #puts "#{@cursor[0]}) = #{data.bytes.map { |n| "%02x" % n }.join}"
171
+
172
+ trace(cursor_start, data.bytes, current.name)
78
173
  data
79
174
  end
80
175
 
@@ -83,49 +178,68 @@ class Innodb::Cursor
83
178
  read_and_advance(length)
84
179
  end
85
180
 
181
+ def each_byte_as_uint8(length)
182
+ unless block_given?
183
+ return enum_for(:each_byte_as_uint8, length)
184
+ end
185
+
186
+ read_and_advance(length).bytes.each do |byte|
187
+ yield byte
188
+ end
189
+
190
+ nil
191
+ end
192
+
86
193
  # Return raw bytes as hex.
87
194
  def get_hex(length)
88
195
  read_and_advance(length).bytes.map { |c| "%02x" % c }.join
89
196
  end
90
197
 
91
198
  # Read an unsigned 8-bit integer.
92
- def get_uint8(offset=nil)
93
- seek(offset)
199
+ def get_uint8(position=nil)
200
+ seek(position)
94
201
  data = read_and_advance(1)
95
202
  BinData::Uint8.read(data)
96
203
  end
97
204
 
98
205
  # Read a big-endian unsigned 16-bit integer.
99
- def get_uint16(offset=nil)
100
- seek(offset)
206
+ def get_uint16(position=nil)
207
+ seek(position)
101
208
  data = read_and_advance(2)
102
209
  BinData::Uint16be.read(data)
103
210
  end
104
211
 
105
212
  # Read a big-endian signed 16-bit integer.
106
- def get_sint16(offset=nil)
107
- seek(offset)
213
+ def get_sint16(position=nil)
214
+ seek(position)
108
215
  data = read_and_advance(2)
109
216
  BinData::Int16be.read(data)
110
217
  end
111
218
 
112
219
  # Read a big-endian unsigned 24-bit integer.
113
- def get_uint24(offset=nil)
114
- seek(offset)
220
+ def get_uint24(position=nil)
221
+ seek(position)
115
222
  data = read_and_advance(3)
116
223
  BinData::Uint24be.read(data)
117
224
  end
118
225
 
119
226
  # Read a big-endian unsigned 32-bit integer.
120
- def get_uint32(offset=nil)
121
- seek(offset)
227
+ def get_uint32(position=nil)
228
+ seek(position)
122
229
  data = read_and_advance(4)
123
230
  BinData::Uint32be.read(data)
124
231
  end
125
232
 
233
+ # Read a big-endian unsigned 48-bit integer.
234
+ def get_uint48(position=nil)
235
+ seek(position)
236
+ data = read_and_advance(6)
237
+ BinData::Uint48be.read(data)
238
+ end
239
+
126
240
  # Read a big-endian unsigned 64-bit integer.
127
- def get_uint64(offset=nil)
128
- seek(offset)
241
+ def get_uint64(position=nil)
242
+ seek(position)
129
243
  data = read_and_advance(8)
130
244
  BinData::Uint64be.read(data)
131
245
  end
@@ -141,6 +255,8 @@ class Innodb::Cursor
141
255
  get_uint24
142
256
  when 4
143
257
  get_uint32
258
+ when 6
259
+ get_uint48
144
260
  when 8
145
261
  get_uint64
146
262
  else
@@ -148,6 +264,10 @@ class Innodb::Cursor
148
264
  end
149
265
  end
150
266
 
267
+ def get_uint_array_by_size(size, count)
268
+ (0...count).to_a.inject([]) { |a, n| a << get_uint_by_size(size); a }
269
+ end
270
+
151
271
  # Read an InnoDB-compressed unsigned 32-bit integer.
152
272
  def get_ic_uint32
153
273
  flag = peek { get_uint8 }
@@ -193,6 +313,12 @@ class Innodb::Cursor
193
313
  BinData::Int32be.read(data) ^ (-1 << 31)
194
314
  end
195
315
 
316
+ # Read an InnoDB-munged signed 48-bit integer.
317
+ def get_i_sint48
318
+ data = read_and_advance(6)
319
+ BinData::Int48be.read(data) ^ (-1 << 47)
320
+ end
321
+
196
322
  # Read an InnoDB-munged signed 64-bit integer.
197
323
  def get_i_sint64
198
324
  data = read_and_advance(8)
@@ -210,6 +336,8 @@ class Innodb::Cursor
210
336
  get_i_sint24
211
337
  when 4
212
338
  get_i_sint32
339
+ when 6
340
+ get_i_sint48
213
341
  when 8
214
342
  get_i_sint64
215
343
  else