innodb_ruby 0.7.7 → 0.7.10

Sign up to get free protection for your applications and to get access to all the features.
data/bin/innodb_space CHANGED
@@ -229,6 +229,32 @@ def space_extents(space)
229
229
  print_xdes_list(space.each_xdes)
230
230
  end
231
231
 
232
+ def page_directory_summary(page)
233
+ puts "%-8s%-8s%-14s%-8s%s" % [
234
+ "slot",
235
+ "offset",
236
+ "type",
237
+ "owned",
238
+ "key",
239
+ ]
240
+
241
+ page.directory.each_with_index do |offset, slot|
242
+ record = page.record(offset)
243
+ key = if [:conventional, :node_pointer].include? record[:header][:type]
244
+ if record[:key]
245
+ "(%s)" % record[:key].join(", ")
246
+ end
247
+ end
248
+ puts "%-8i%-8i%-14s%-8i%s" % [
249
+ slot,
250
+ offset,
251
+ record[:header][:type],
252
+ record[:header][:n_owned],
253
+ key,
254
+ ]
255
+ end
256
+ end
257
+
232
258
  def index_fseg_lists(index, fseg_name)
233
259
  unless index.fseg(fseg_name)
234
260
  raise "File segment '#{fseg_name}' doesn't exist"
@@ -362,8 +388,8 @@ Usage: innodb_space -f <file> [-p <page>] [-l <level>] <mode> [<mode>, ...]
362
388
  Load the tablespace file <file>.
363
389
 
364
390
  --page-size, -P <size>
365
- Provide the page size (in KiB): 16 (the default), 8, 4, 2, 1. Page sizes
366
- other than 16 may not work well, or at all.
391
+ Provide the page size, overriding auto-detection (in KiB): 16, 8, 4, 2, 1.
392
+ Page sizes other than 16 may not work well, or at all.
367
393
 
368
394
  --page, -p <page>
369
395
  Operate on the page <page>; may be specified more than once.
@@ -383,9 +409,6 @@ Usage: innodb_space -f <file> [-p <page>] [-l <level>] <mode> [<mode>, ...]
383
409
 
384
410
  The following modes are supported:
385
411
 
386
- page-dump
387
- Dump the contents of the page, using the Ruby pp ("pretty-print") module.
388
-
389
412
  space-summary
390
413
  Summarize all pages within a tablespace. A starting page number can be
391
414
  provided with the --page/-p argument.
@@ -421,6 +444,13 @@ The following modes are supported:
421
444
  space-extents
422
445
  Iterate through all extents, printing the extent descriptor bitmap.
423
446
 
447
+ page-dump
448
+ Dump the contents of a page, using the Ruby pp ("pretty-print") module.
449
+
450
+ page-directory-summary
451
+ Summarize the record contents of the page directory in a page. If a record
452
+ describer is available, the key of each record will be printed.
453
+
424
454
  index-recurse
425
455
  Recurse an index, starting at the root (which must be provided in the first
426
456
  --page/-p argument), printing the node pages, node pointers (links), leaf
@@ -457,9 +487,12 @@ END_OF_USAGE
457
487
  exit exit_code
458
488
  end
459
489
 
490
+ Signal.trap("INT") { exit }
491
+ Signal.trap("PIPE") { exit }
492
+
460
493
  @options = OpenStruct.new
461
494
  @options.file = nil
462
- @options.page_size = Innodb::Space::DEFAULT_PAGE_SIZE
495
+ @options.page_size = nil
463
496
  @options.pages = []
464
497
  @options.levels = []
465
498
  @options.lists = []
@@ -525,9 +558,20 @@ ARGV.each do |mode|
525
558
  usage 1, "Page numbers to dump must be provided with --page/-p"
526
559
  end
527
560
 
528
- @options.pages.each do |page|
529
- space.page(page).dump
561
+ @options.pages.each do |page_number|
562
+ space.page(page_number).dump
530
563
  end
564
+ when "page-directory-summary"
565
+ if @options.pages.empty?
566
+ usage 1, "Page numbers to dump must be provided with --page/-p"
567
+ end
568
+
569
+ page = space.page(@options.pages.first)
570
+ if page.type != :INDEX
571
+ usage 1, "Page must be an index page"
572
+ end
573
+
574
+ page_directory_summary(page)
531
575
  when "space-summary"
532
576
  space_summary(space, @options.pages.first || 0)
533
577
  when "space-index-pages-summary"
data/lib/innodb/index.rb CHANGED
@@ -187,10 +187,10 @@ class Innodb::Index
187
187
  this_rec = cursor.record
188
188
 
189
189
  if @debug
190
- puts "linear_search_from_cursor: start=(%s), page=%i, level=%i" % [
191
- this_rec && this_rec[:key].join(", "),
190
+ puts "linear_search_from_cursor: page=%i, level=%i, start=(%s)" % [
192
191
  page.offset,
193
192
  page.level,
193
+ this_rec && this_rec[:key].join(", "),
194
194
  ]
195
195
  end
196
196
 
@@ -200,7 +200,9 @@ class Innodb::Index
200
200
  @stats[:linear_search_from_cursor_record_scans] += 1
201
201
 
202
202
  if @debug
203
- puts "linear_search_from_cursor: scanning: current=(%s)" % [
203
+ puts "linear_search_from_cursor: page=%i, level=%i, current=(%s)" % [
204
+ page.offset,
205
+ page.level,
204
206
  this_rec && this_rec[:key].join(", "),
205
207
  ]
206
208
  end
@@ -208,6 +210,10 @@ class Innodb::Index
208
210
  # If we reach supremum, return the last non-system record we got.
209
211
  return this_rec if next_rec[:header][:type] == :supremum
210
212
 
213
+ if compare_key(key, this_rec[:key]) < 0
214
+ return this_rec
215
+ end
216
+
211
217
  if (compare_key(key, this_rec[:key]) >= 0) &&
212
218
  (compare_key(key, next_rec[:key]) < 0)
213
219
  # The desired key is either an exact match for this_rec or is greater
@@ -235,19 +241,21 @@ class Innodb::Index
235
241
 
236
242
  return nil if dir.empty?
237
243
 
244
+ # Split the directory at the mid-point (using integer math, so the division
245
+ # is rounding down). Retrieve the record that sits at the mid-point.
246
+ mid = ((dir.size-1) / 2)
247
+ rec = page.record(dir[mid])
248
+
238
249
  if @debug
239
- puts "binary_search_by_directory: page=%i, level=%i, dir.size=%i" % [
250
+ puts "binary_search_by_directory: page=%i, level=%i, dir.size=%i, dir[%i]=(%s)" % [
240
251
  page.offset,
241
252
  page.level,
242
253
  dir.size,
254
+ mid,
255
+ rec[:key] && rec[:key].join(", "),
243
256
  ]
244
257
  end
245
258
 
246
- # Split the directory at the mid-point (using integer math, so the division
247
- # is rounding down). Retrieve the record that sits at the mid-point.
248
- mid = dir.size / 2
249
- rec = page.record(dir[mid])
250
-
251
259
  # The mid-point record was the infimum record, which is not comparable with
252
260
  # compare_key, so we need to just linear scan from here. If the mid-point
253
261
  # is the beginning of the page there can't be many records left to check
@@ -264,18 +272,29 @@ class Innodb::Index
264
272
  rec
265
273
  when +1
266
274
  # The mid-point record's key is less than the desired key.
267
- if dir.size == 1
268
- # This is the last entry remaining from the directory, use linear
269
- # search to find the record.
270
- @stats[:binary_search_by_directory_linear_search] += 1
271
- linear_search_from_cursor(page, page.record_cursor(rec[:offset]), key)
272
- else
275
+ if dir.size > 2
273
276
  # There are more entries remaining from the directory, recurse again
274
277
  # using binary search on the right half of the directory, which
275
278
  # represents values greater than or equal to the mid-point record's
276
279
  # key.
277
280
  @stats[:binary_search_by_directory_recurse_right] += 1
278
281
  binary_search_by_directory(page, dir[mid...dir.size], key)
282
+ else
283
+ next_rec = page.record(dir[mid+1])
284
+ next_key = next_rec && compare_key(key, next_rec[:key])
285
+ if dir.size == 1 || next_key == -1 || next_key == 0
286
+ # This is the last entry remaining from the directory, or our key is
287
+ # greater than rec and less than rec+1's key. Use linear search to
288
+ # find the record starting at rec.
289
+ @stats[:binary_search_by_directory_linear_search] += 1
290
+ linear_search_from_cursor(page, page.record_cursor(rec[:offset]), key)
291
+ elsif next_key == +1
292
+ puts "+1"
293
+ @stats[:binary_search_by_directory_linear_search] += 1
294
+ linear_search_from_cursor(page, page.record_cursor(next_rec[:offset]), key)
295
+ else
296
+ nil
297
+ end
279
298
  end
280
299
  when -1
281
300
  # The mid-point record's key is greater than the desired key.
@@ -304,10 +323,10 @@ class Innodb::Index
304
323
  page = @root
305
324
 
306
325
  if @debug
307
- puts "linear_search: key=(%s), root=%i, level=%i" % [
308
- key.join(", "),
326
+ puts "linear_search: root=%i, level=%i, key=(%s)" % [
309
327
  page.offset,
310
328
  page.level,
329
+ key.join(", "),
311
330
  ]
312
331
  end
313
332
 
@@ -336,14 +355,16 @@ class Innodb::Index
336
355
  page = @root
337
356
 
338
357
  if @debug
339
- puts "binary_search: key=(%s), root=%i, level=%i" % [
340
- key.join(", "),
358
+ puts "binary_search: root=%i, level=%i, key=(%s)" % [
341
359
  page.offset,
342
360
  page.level,
361
+ key.join(", "),
343
362
  ]
344
363
  end
345
364
 
346
- while rec = binary_search_by_directory(page, page.directory, key)
365
+ # Remove supremum from the page directory, since nothing can be scanned
366
+ # linearly from there anyway.
367
+ while rec = binary_search_by_directory(page, page.directory[0...-1], key)
347
368
  if page.level > 0
348
369
  # If we haven't reached a leaf page yet, move down the tree and search
349
370
  # again using binary search.
@@ -11,6 +11,45 @@ require "innodb/xdes"
11
11
  # The basic structure of FSP_HDR and XDES pages is: FIL header, FSP header,
12
12
  # an array of 256 XDES entries, empty (unused) space, and FIL trailer.
13
13
  class Innodb::Page::FspHdrXdes < Innodb::Page
14
+ # A value added to the adjusted exponent stored in the page size field of
15
+ # the flags in the FSP header.
16
+ FLAGS_PAGE_SIZE_ADJUST = 9
17
+
18
+ # Read a given number of bits from an integer at a specific bit offset. The
19
+ # value returned is 0-based so does not need further shifting or adjustment.
20
+ def self.read_bits_at_offset(data, bits, offset)
21
+ ((data & (((1 << bits) - 1) << offset)) >> offset)
22
+ end
23
+
24
+ # Decode the "flags" field in the FSP header, returning a hash of useful
25
+ # decoded flags. Unfortunately, InnoDB has a fairly weird and broken
26
+ # implementation of these flags. The flags are:
27
+ #
28
+ # Offset Size Description
29
+ # 0 1 Page Format (redundant, compact). This is unfortunately
30
+ # coerced to 0 if it is "compact" and no other flags are
31
+ # set, making it useless to innodb_ruby.
32
+ # 1 4 Compressed Page Size (zip_size). This is stored as a
33
+ # power of 2, minus 9. Since 0 is reserved to mean "not
34
+ # compressed", the minimum value is 1, thus making the
35
+ # smallest page size 1024 (2 ** (9 + 1)).
36
+ # 5 1 Table Format (Antelope, Barracuda). This was supposed
37
+ # to reserve 6 bits, but due to a bug in InnoDB only
38
+ # actually reserved 1 bit.
39
+ #
40
+ def self.decode_flags(flags)
41
+ # The page size for compressed pages is stored at bit offset 1 and consumes
42
+ # 4 bits. Value 0 means the page is not compressed.
43
+ page_size = read_bits_at_offset(flags, 4, 1)
44
+ {
45
+ :compressed => page_size == 0 ? false : true,
46
+ :page_size => page_size == 0 ?
47
+ Innodb::Space::DEFAULT_PAGE_SIZE :
48
+ (1 << (FLAGS_PAGE_SIZE_ADJUST + page_size)),
49
+ :value => flags,
50
+ }
51
+ end
52
+
14
53
  # The FSP header immediately follows the FIL header.
15
54
  def pos_fsp_header
16
55
  pos_fil_header + size_fil_header
@@ -42,7 +81,7 @@ class Innodb::Page::FspHdrXdes < Innodb::Page
42
81
  :unused => c.get_uint32,
43
82
  :size => c.get_uint32,
44
83
  :free_limit => c.get_uint32,
45
- :flags => c.get_uint32,
84
+ :flags => self.class.decode_flags(c.get_uint32),
46
85
  :frag_n_used => c.get_uint32,
47
86
  :free => Innodb::List::Xdes.new(@space,
48
87
  Innodb::List.get_base_node(c)),
data/lib/innodb/space.rb CHANGED
@@ -4,13 +4,20 @@ class Innodb::Space
4
4
  # InnoDB's default page size is 16KiB.
5
5
  DEFAULT_PAGE_SIZE = 16384
6
6
 
7
- # Open a tablespace file, providing the page size to use. Pages that aren't
8
- # 16 KiB may not be supported well.
9
- def initialize(file, page_size=DEFAULT_PAGE_SIZE)
7
+ # Open a tablespace file, optionally providing the page size to use. Pages
8
+ # that aren't 16 KiB may not be supported well.
9
+ def initialize(file, page_size=nil)
10
10
  @file = File.open(file)
11
- @page_size = page_size
12
11
  @size = @file.stat.size
13
- @pages = (@size / page_size)
12
+
13
+ if page_size
14
+ @page_size = page_size
15
+ else
16
+ @page_size = fsp_flags[:page_size]
17
+ end
18
+
19
+ @pages = (@size / @page_size)
20
+ @compressed = fsp_flags[:compressed]
14
21
  @record_describer = nil
15
22
  end
16
23
 
@@ -27,6 +34,42 @@ class Innodb::Space
27
34
  # The number of pages in the space.
28
35
  attr_reader :pages
29
36
 
37
+ # Read the FSP header "flags" field by byte offset within the space file.
38
+ # This is useful in order to initialize the page size, as we can't properly
39
+ # read the FSP_HDR page before we know its size.
40
+ def raw_fsp_header_flags
41
+ # A simple sanity check. The FIL header should be initialized in page 0,
42
+ # to offset 0 and page type :FSP_HDR (8).
43
+ page_offset = BinData::Uint32be.read(read_at_offset(4, 4))
44
+ page_type = BinData::Uint16be.read(read_at_offset(24, 2))
45
+ unless page_offset == 0 && Innodb::Page::PAGE_TYPE[page_type] == :FSP_HDR
46
+ raise "Something is very wrong; Page 0 does not seem to be type FSP_HDR"
47
+ end
48
+
49
+ # Another sanity check. The Space ID should be the same in both the FIL
50
+ # and FSP headers.
51
+ fil_space = BinData::Uint32be.read(read_at_offset(34, 4))
52
+ fsp_space = BinData::Uint32be.read(read_at_offset(38, 4))
53
+ unless fil_space == fsp_space
54
+ raise "Something is very wrong; FIL and FSP header Space IDs don't match"
55
+ end
56
+
57
+ # Well, we're as sure as we can be. Read the flags field and decode it.
58
+ flags_value = BinData::Uint32be.read(read_at_offset(54, 4))
59
+ Innodb::Page::FspHdrXdes.decode_flags(flags_value)
60
+ end
61
+
62
+ # The FSP header flags, decoded. If the page size has not been initialized,
63
+ # reach into the raw bytes of the FSP_HDR page and attempt to decode the
64
+ # flags field that way.
65
+ def fsp_flags
66
+ if @page_size
67
+ return fsp[:flags]
68
+ else
69
+ raw_fsp_header_flags
70
+ end
71
+ end
72
+
30
73
  # The size (in bytes) of an extent.
31
74
  def extent_size
32
75
  1048576
@@ -48,14 +91,23 @@ class Innodb::Space
48
91
  (0..(@pages / pages_per_xdes_page)).map { |n| n * pages_per_xdes_page }
49
92
  end
50
93
 
51
- # Get an Innodb::Page object for a specific page by page number.
52
- def page(page_number)
94
+ # Get the raw byte buffer of size bytes at offset in the file.
95
+ def read_at_offset(offset, size)
96
+ @file.seek(offset)
97
+ @file.read(size)
98
+ end
99
+
100
+ # Get the raw byte buffer for a specific page by page number.
101
+ def page_data(page_number)
53
102
  offset = page_number.to_i * page_size
54
103
  return nil unless offset < @size
55
104
  return nil unless (offset + page_size) <= @size
56
- @file.seek(offset)
57
- page_data = @file.read(page_size)
58
- this_page = Innodb::Page.parse(self, page_data)
105
+ read_at_offset(offset, page_size)
106
+ end
107
+
108
+ # Get an Innodb::Page object for a specific page by page number.
109
+ def page(page_number)
110
+ this_page = Innodb::Page.parse(self, page_data(page_number))
59
111
 
60
112
  if this_page.type == :INDEX
61
113
  this_page.record_describer = @record_describer
@@ -64,9 +116,14 @@ class Innodb::Space
64
116
  this_page
65
117
  end
66
118
 
119
+ # Get (and cache) the FSP header from the FSP_HDR page.
120
+ def fsp
121
+ @fsp ||= page(0).fsp_header
122
+ end
123
+
67
124
  # Get an Innodb::List object for a specific list by list name.
68
125
  def list(name)
69
- page(0).fsp_header[name]
126
+ fsp[name]
70
127
  end
71
128
 
72
129
  # Get an Innodb::Index object for a specific index by root page number.
@@ -1,3 +1,3 @@
1
1
  module Innodb
2
- VERSION = "0.7.7"
2
+ VERSION = "0.7.10"
3
3
  end
metadata CHANGED
@@ -1,48 +1,35 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: innodb_ruby
3
- version: !ruby/object:Gem::Version
4
- hash: 13
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.7.10
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 7
9
- - 7
10
- version: 0.7.7
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Jeremy Cole
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2013-01-09 00:00:00 Z
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
12
+ date: 2013-01-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
21
15
  name: bindata
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: &70135906385360 !ruby/object:Gem::Requirement
24
17
  none: false
25
- requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- hash: 13
29
- segments:
30
- - 1
31
- - 4
32
- - 5
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
33
21
  version: 1.4.5
34
22
  type: :runtime
35
- version_requirements: *id001
23
+ prerelease: false
24
+ version_requirements: *70135906385360
36
25
  description: Library for parsing InnoDB data files in Ruby
37
26
  email: jeremy@jcole.us
38
- executables:
27
+ executables:
39
28
  - innodb_log
40
29
  - innodb_space
41
30
  extensions: []
42
-
43
31
  extra_rdoc_files: []
44
-
45
- files:
32
+ files:
46
33
  - README.md
47
34
  - lib/innodb.rb
48
35
  - lib/innodb/cursor.rb
@@ -65,37 +52,27 @@ files:
65
52
  - bin/innodb_space
66
53
  homepage: http://jcole.us/
67
54
  licenses: []
68
-
69
55
  post_install_message:
70
56
  rdoc_options: []
71
-
72
- require_paths:
57
+ require_paths:
73
58
  - lib
74
- required_ruby_version: !ruby/object:Gem::Requirement
59
+ required_ruby_version: !ruby/object:Gem::Requirement
75
60
  none: false
76
- requirements:
77
- - - ">="
78
- - !ruby/object:Gem::Version
79
- hash: 3
80
- segments:
81
- - 0
82
- version: "0"
83
- required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ! '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
66
  none: false
85
- requirements:
86
- - - ">="
87
- - !ruby/object:Gem::Version
88
- hash: 3
89
- segments:
90
- - 0
91
- version: "0"
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
92
71
  requirements: []
93
-
94
72
  rubyforge_project:
95
- rubygems_version: 1.8.10
73
+ rubygems_version: 1.8.6
96
74
  signing_key:
97
75
  specification_version: 3
98
76
  summary: InnoDB data file parser
99
77
  test_files: []
100
-
101
78
  has_rdoc: