innodb_ruby 0.7.11 → 0.7.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -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