innodb_ruby 0.7.7 → 0.7.10

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.
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: