innodb_ruby 0.7.3 → 0.7.4

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
@@ -113,13 +113,13 @@ def space_indexes(space)
113
113
  used =
114
114
  fragments +
115
115
  fseg[:not_full_n_used] +
116
- Innodb::Xdes::PAGES_PER_EXTENT * fseg[:full].base[:length]
116
+ space.pages_per_extent * fseg[:full].base[:length]
117
117
 
118
118
  allocated =
119
119
  fragments +
120
- Innodb::Xdes::PAGES_PER_EXTENT * fseg[:full].base[:length] +
121
- Innodb::Xdes::PAGES_PER_EXTENT * fseg[:not_full].base[:length] +
122
- Innodb::Xdes::PAGES_PER_EXTENT * fseg[:free].base[:length]
120
+ space.pages_per_extent * fseg[:full].base[:length] +
121
+ space.pages_per_extent * fseg[:not_full].base[:length] +
122
+ space.pages_per_extent * fseg[:free].base[:length]
123
123
 
124
124
  puts "%-12i%-12i%-12s%-12i%-12i%-12s" % [
125
125
  index.id,
@@ -206,7 +206,7 @@ def space_index_pages_free_plot(space, image)
206
206
 
207
207
  image_file = image + "_free.png"
208
208
  # Aim for one horizontal pixel per extent, but min 1k and max 10k width.
209
- image_width = [10000, [1000, space.pages / 64].max].min
209
+ image_width = [10000, [1000, space.pages / space.pages_per_extent].max].min
210
210
 
211
211
  Gnuplot.open do |gp|
212
212
  Gnuplot::Plot.new(gp) do |plot|
@@ -334,6 +334,10 @@ Usage: innodb_space -f <file> [-p <page>] [-l <level>] <mode> [<mode>, ...]
334
334
  --file, -f <file>
335
335
  Load the tablespace file <file>.
336
336
 
337
+ --page-size, -P <size>
338
+ Provide the page size (in KiB): 16 (the default), 8, 4, 2, 1. Page sizes
339
+ other than 16 may not work well, or at all.
340
+
337
341
  --page, -p <page>
338
342
  Operate on the page <page>; may be specified more than once.
339
343
 
@@ -411,6 +415,7 @@ end
411
415
 
412
416
  @options = OpenStruct.new
413
417
  @options.file = nil
418
+ @options.page_size = Innodb::Space::DEFAULT_PAGE_SIZE
414
419
  @options.pages = []
415
420
  @options.levels = []
416
421
  @options.lists = []
@@ -419,6 +424,7 @@ end
419
424
  getopt_options = [
420
425
  [ "--help", "-?", GetoptLong::NO_ARGUMENT ],
421
426
  [ "--file", "-f", GetoptLong::REQUIRED_ARGUMENT ],
427
+ [ "--page-size", "-P", GetoptLong::REQUIRED_ARGUMENT ],
422
428
  [ "--page", "-p", GetoptLong::REQUIRED_ARGUMENT ],
423
429
  [ "--level", "-l", GetoptLong::REQUIRED_ARGUMENT ],
424
430
  [ "--list", "-L", GetoptLong::REQUIRED_ARGUMENT ],
@@ -436,6 +442,11 @@ getopt.each do |opt, arg|
436
442
  @options.mode = arg
437
443
  when "--file"
438
444
  @options.file = arg
445
+ when "--page-size"
446
+ unless [1, 2, 4, 8, 16].include?(arg.to_i)
447
+ usage 1, "Page size #{arg} is not understood"
448
+ end
449
+ @options.page_size = arg.to_i * 1024
439
450
  when "--page"
440
451
  @options.pages << arg.to_i
441
452
  when "--level"
@@ -453,7 +464,7 @@ unless @options.file
453
464
  usage 1, "File must be provided with -f argument"
454
465
  end
455
466
 
456
- space = Innodb::Space.new(@options.file)
467
+ space = Innodb::Space.new(@options.file, @options.page_size)
457
468
 
458
469
  if @options.describer
459
470
  space.record_describer = Innodb::RecordDescriber.const_get(@options.describer)
data/lib/innodb/index.rb CHANGED
@@ -1,10 +1,13 @@
1
1
  # An InnoDB index B-tree, given an Innodb::Space and a root page number.
2
2
  class Innodb::Index
3
3
  attr_reader :root
4
+ attr_reader :stats
5
+ attr_accessor :debug
4
6
 
5
7
  def initialize(space, root_page_number)
6
8
  @space = space
7
9
  @root = @space.page(root_page_number)
10
+ @debug = false
8
11
 
9
12
  unless @root
10
13
  raise "Page #{root_page_number} couldn't be read"
@@ -19,6 +22,12 @@ class Innodb::Index
19
22
  unless @root.prev.nil? && @root.next.nil?
20
23
  raise "Page #{root_page_number} is a node page, but not appear to be the root; it has previous page and next page pointers"
21
24
  end
25
+
26
+ reset_stats
27
+ end
28
+
29
+ def reset_stats
30
+ @stats = Hash.new(0)
22
31
  end
23
32
 
24
33
  # A helper function to access the index ID in the page header.
@@ -114,11 +123,14 @@ class Innodb::Index
114
123
  # -1 = a is less than b
115
124
  # +1 = a is greater than b
116
125
  def compare_key(a, b)
126
+ @stats[:compare_key] += 1
127
+
117
128
  return 0 if a.nil? && b.nil?
118
129
  return -1 if a.nil? || (!b.nil? && a.size < b.size)
119
130
  return +1 if b.nil? || (!a.nil? && a.size > b.size)
120
131
 
121
132
  a.each_index do |i|
133
+ @stats[:compare_key_field_comparison] += 1
122
134
  return -1 if a[i] < b[i]
123
135
  return +1 if a[i] > b[i]
124
136
  end
@@ -131,12 +143,30 @@ class Innodb::Index
131
143
  # than the key. (If an exact match is desired, compare_key must be used to
132
144
  # check if the returned record matches. This makes the function useful for
133
145
  # search in both leaf and non-leaf pages.)
134
- def linear_search_from_cursor(cursor, key)
146
+ def linear_search_from_cursor(page, cursor, key)
147
+ @stats[:linear_search_from_cursor] += 1
148
+
135
149
  this_rec = cursor.record
136
150
 
151
+ if @debug
152
+ puts "linear_search_from_cursor: start=(%s), page=%i, level=%i" % [
153
+ this_rec && this_rec[:key].join(", "),
154
+ page.offset,
155
+ page.level,
156
+ ]
157
+ end
158
+
137
159
  # Iterate through all records until finding either a matching record or
138
160
  # one whose key is greater than the desired key.
139
161
  while this_rec && next_rec = cursor.record
162
+ @stats[:linear_search_from_cursor_record_scans] += 1
163
+
164
+ if @debug
165
+ puts "linear_search_from_cursor: scanning: current=(%s)" % [
166
+ this_rec && this_rec[:key].join(", "),
167
+ ]
168
+ end
169
+
140
170
  # If we reach supremum, return the last non-system record we got.
141
171
  return this_rec if next_rec[:header][:type] == :supremum
142
172
 
@@ -163,8 +193,18 @@ class Innodb::Index
163
193
  # desired, the returned record must be checked in the same way as the above
164
194
  # linear_search_from_cursor function.)
165
195
  def binary_search_by_directory(page, dir, key)
196
+ @stats[:binary_search_by_directory] += 1
197
+
166
198
  return nil if dir.empty?
167
199
 
200
+ if @debug
201
+ puts "binary_search_by_directory: page=%i, level=%i, dir.size=%i" % [
202
+ page.offset,
203
+ page.level,
204
+ dir.size,
205
+ ]
206
+ end
207
+
168
208
  # Split the directory at the mid-point (using integer math, so the division
169
209
  # is rounding down). Retrieve the record that sits at the mid-point.
170
210
  mid = dir.size / 2
@@ -175,27 +215,28 @@ class Innodb::Index
175
215
  # is the beginning of the page there can't be many records left to check
176
216
  # anyway.
177
217
  if rec[:header][:type] == :infimum
178
- return linear_search_from_cursor(page.record_cursor(rec[:next]), key)
218
+ return linear_search_from_cursor(page, page.record_cursor(rec[:next]), key)
179
219
  end
180
220
 
181
221
  # Compare the desired key to the mid-point record's key.
182
222
  case compare_key(key, rec[:key])
183
223
  when 0
184
224
  # An exact match for the key was found. Return the record.
225
+ @stats[:binary_search_by_directory_exact_match] += 1
185
226
  rec
186
227
  when +1
187
228
  # The mid-point record's key is less than the desired key.
188
229
  if dir.size == 1
189
230
  # This is the last entry remaining from the directory, use linear
190
- # search to find the record. We already know that there wasn't an
191
- # exact match, so skip the current record and start cursoring from
192
- # the next record.
193
- linear_search_from_cursor(page.record_cursor(rec[:next]), key)
231
+ # search to find the record.
232
+ @stats[:binary_search_by_directory_linear_search] += 1
233
+ linear_search_from_cursor(page, page.record_cursor(rec[:offset]), key)
194
234
  else
195
235
  # There are more entries remaining from the directory, recurse again
196
236
  # using binary search on the right half of the directory, which
197
237
  # represents values greater than or equal to the mid-point record's
198
238
  # key.
239
+ @stats[:binary_search_by_directory_recurse_right] += 1
199
240
  binary_search_by_directory(page, dir[mid...dir.size], key)
200
241
  end
201
242
  when -1
@@ -203,10 +244,12 @@ class Innodb::Index
203
244
  if dir.size == 1
204
245
  # If this is the last entry remaining from the directory, we didn't
205
246
  # find anything workable.
247
+ @stats[:binary_search_by_directory_empty_result] += 1
206
248
  nil
207
249
  else
208
250
  # Recurse on the left half of the directory, which represents values
209
251
  # less than the mid-point record's key.
252
+ @stats[:binary_search_by_directory_recurse_left] += 1
210
253
  binary_search_by_directory(page, dir[0...mid], key)
211
254
  end
212
255
  end
@@ -218,10 +261,20 @@ class Innodb::Index
218
261
  # record is not found, nil is returned (either because linear_search_in_page
219
262
  # returns nil breaking the loop, or because compare_key returns non-zero).
220
263
  def linear_search(key)
264
+ @stats[:linear_search] += 1
265
+
221
266
  page = @root
222
267
 
268
+ if @debug
269
+ puts "linear_search: key=(%s), root=%i, level=%i" % [
270
+ key.join(", "),
271
+ page.offset,
272
+ page.level,
273
+ ]
274
+ end
275
+
223
276
  while rec =
224
- linear_search_from_cursor(page.record_cursor(page.infimum[:next]), key)
277
+ linear_search_from_cursor(page, page.record_cursor(page.infimum[:next]), key)
225
278
  if page.level > 0
226
279
  # If we haven't reached a leaf page yet, move down the tree and search
227
280
  # again using linear search.
@@ -240,8 +293,18 @@ class Innodb::Index
240
293
  # the page directory to search while making as few record comparisons as
241
294
  # possible. If a matching record is not found, nil is returned.
242
295
  def binary_search(key)
296
+ @stats[:binary_search] += 1
297
+
243
298
  page = @root
244
299
 
300
+ if @debug
301
+ puts "binary_search: key=(%s), root=%i, level=%i" % [
302
+ key.join(", "),
303
+ page.offset,
304
+ page.level,
305
+ ]
306
+ end
307
+
245
308
  while rec = binary_search_by_directory(page, page.directory, key)
246
309
  if page.level > 0
247
310
  # If we haven't reached a leaf page yet, move down the tree and search
data/lib/innodb/page.rb CHANGED
@@ -32,13 +32,19 @@ class Innodb::Page
32
32
  page
33
33
  end
34
34
 
35
- # Initialize a page by passing in a 16kB buffer containing the raw page
36
- # contents. Currently only 16kB pages are supported.
35
+ # Initialize a page by passing in a buffer containing the raw page contents.
36
+ # The buffer size should match the space's page size.
37
37
  def initialize(space, buffer)
38
+ unless space.page_size == buffer.size
39
+ raise "Buffer size #{buffer.size} is different than space page size"
40
+ end
41
+
38
42
  @space = space
39
43
  @buffer = buffer
40
44
  end
41
45
 
46
+ attr_reader :space
47
+
42
48
  # Return the page size, to eventually be able to deal with non-16kB pages.
43
49
  def size
44
50
  @size ||= @buffer.size
@@ -11,10 +11,6 @@ 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
- # This is actually defined as page size divided by extent size, which is
15
- # 16384 / 64 = 256.
16
- XDES_N_ARRAY_ENTRIES = 256
17
-
18
14
  # The FSP header immediately follows the FIL header.
19
15
  def pos_fsp_header
20
16
  pos_fil_header + size_fil_header
@@ -31,6 +27,12 @@ class Innodb::Page::FspHdrXdes < Innodb::Page
31
27
  pos_fsp_header + size_fsp_header
32
28
  end
33
29
 
30
+ # The number of entries in the XDES array. Defined as page size divided by
31
+ # extent size.
32
+ def entries_in_xdes_array
33
+ size / space.pages_per_extent
34
+ end
35
+
34
36
  # Read the FSP (filespace) header, which contains a few counters and flags,
35
37
  # as well as list base nodes for each list maintained in the filespace.
36
38
  def fsp_header
@@ -66,7 +68,7 @@ class Innodb::Page::FspHdrXdes < Innodb::Page
66
68
  end
67
69
 
68
70
  c = cursor(pos_xdes_array)
69
- XDES_N_ARRAY_ENTRIES.times do
71
+ entries_in_xdes_array.times do
70
72
  yield Innodb::Xdes.new(self, c)
71
73
  end
72
74
  end
@@ -45,7 +45,7 @@ class Innodb::Page::Inode < Innodb::Page
45
45
 
46
46
  # Return the list entry.
47
47
  def list_entry
48
- c = cursor(pos_inode_list_entry)
48
+ c = cursor(pos_list_entry)
49
49
  Innodb::List.get_node(c)
50
50
  end
51
51
 
data/lib/innodb/space.rb CHANGED
@@ -1,27 +1,49 @@
1
1
  # An InnoDB tablespace file, which can be either a multi-table ibdataN file
2
2
  # or a single-table "innodb_file_per_table" .ibd file.
3
3
  class Innodb::Space
4
- attr_accessor :record_describer
5
- attr_reader :pages
4
+ # InnoDB's default page size is 16KiB.
5
+ DEFAULT_PAGE_SIZE = 16384
6
6
 
7
- # Currently only 16kB InnoDB pages are supported.
8
- PAGE_SIZE = 16384
9
-
10
- # Open a tablespace file.
11
- def initialize(file)
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)
12
10
  @file = File.open(file)
11
+ @page_size = page_size
13
12
  @size = @file.stat.size
14
- @pages = (@size / PAGE_SIZE)
13
+ @pages = (@size / page_size)
15
14
  @record_describer = nil
16
15
  end
17
16
 
17
+ # An object which can be used to describe records found in pages within
18
+ # this space.
19
+ attr_accessor :record_describer
20
+
21
+ # The size (in bytes) of each page in the space.
22
+ attr_reader :page_size
23
+
24
+ # The size (in bytes) of the space
25
+ attr_reader :size
26
+
27
+ # The number of pages in the space.
28
+ attr_reader :pages
29
+
30
+ # The number of pages per extent.
31
+ def pages_per_extent
32
+ 64
33
+ end
34
+
35
+ # The size (in bytes) of an extent.
36
+ def extent_size
37
+ page_size * pages_per_extent
38
+ end
39
+
18
40
  # Get an Innodb::Page object for a specific page by page number.
19
41
  def page(page_number)
20
- offset = page_number.to_i * PAGE_SIZE
42
+ offset = page_number.to_i * page_size
21
43
  return nil unless offset < @size
22
- return nil unless (offset + PAGE_SIZE) <= @size
44
+ return nil unless (offset + page_size) <= @size
23
45
  @file.seek(offset)
24
- page_data = @file.read(PAGE_SIZE)
46
+ page_data = @file.read(page_size)
25
47
  this_page = Innodb::Page.parse(self, page_data)
26
48
 
27
49
  if this_page.type == :INDEX
@@ -51,7 +73,8 @@ class Innodb::Space
51
73
  end
52
74
 
53
75
  (3...@pages).each do |page_number|
54
- if page(page_number).root?
76
+ page = page(page_number)
77
+ if page.type == :INDEX && page.root?
55
78
  yield index(page_number)
56
79
  else
57
80
  break
@@ -1,3 +1,3 @@
1
1
  module Innodb
2
- VERSION = "0.7.3"
2
+ VERSION = "0.7.4"
3
3
  end
data/lib/innodb/xdes.rb CHANGED
@@ -3,10 +3,6 @@
3
3
  #
4
4
  # Note the distinction between +XDES+ _entries_ and +XDES+ _pages_.
5
5
  class Innodb::Xdes
6
- # Number of pages contained in an extent. InnoDB extents are normally
7
- # 64 pages, or 1MiB in size.
8
- PAGES_PER_EXTENT = 64
9
-
10
6
  # Number of bits per page in the +XDES+ entry bitmap field. Currently
11
7
  # +XDES+ entries store two bits per page, with the following meanings:
12
8
  #
@@ -23,12 +19,6 @@ class Innodb::Xdes
23
19
  # The bitwise-OR of all bitmap bit values.
24
20
  BITMAP_BV_ALL = (BITMAP_BV_FREE | BITMAP_BV_CLEAN)
25
21
 
26
- # Size (in bytes) of the bitmap field in the +XDES+ entry.
27
- BITMAP_SIZE = (PAGES_PER_EXTENT * BITS_PER_PAGE) / 8
28
-
29
- # Size (in bytes) of the an +XDES+ entry.
30
- ENTRY_SIZE = 8 + Innodb::List::NODE_SIZE + 4 + BITMAP_SIZE
31
-
32
22
  # The values used in the +:state+ field indicating what the extent is
33
23
  # used for (or what list it is on).
34
24
  STATES = {
@@ -51,15 +41,30 @@ class Innodb::Xdes
51
41
 
52
42
  def initialize(page, cursor)
53
43
  @page = page
54
- extent_number = (cursor.position - page.pos_xdes_array) / ENTRY_SIZE
55
- start_page = page.offset + (extent_number * PAGES_PER_EXTENT)
56
- @xdes = {
44
+ @xdes = read_xdes_entry(page, cursor)
45
+ end
46
+
47
+ # Size (in bytes) of the bitmap field in the +XDES+ entry.
48
+ def size_bitmap
49
+ (@page.space.pages_per_extent * BITS_PER_PAGE) / 8
50
+ end
51
+
52
+ # Size (in bytes) of the an +XDES+ entry.
53
+ def size_entry
54
+ 8 + Innodb::List::NODE_SIZE + 4 + size_bitmap
55
+ end
56
+
57
+ # Read an XDES entry from a cursor.
58
+ def read_xdes_entry(page, cursor)
59
+ extent_number = (cursor.position - page.pos_xdes_array) / size_entry
60
+ start_page = page.offset + (extent_number * page.space.pages_per_extent)
61
+ {
57
62
  :start_page => start_page,
58
63
  :fseg_id => cursor.get_uint64,
59
64
  :this => {:page => page.offset, :offset => cursor.position},
60
65
  :list => Innodb::List.get_node(cursor),
61
66
  :state => STATES[cursor.get_uint32],
62
- :bitmap => cursor.get_bytes(BITMAP_SIZE),
67
+ :bitmap => cursor.get_bytes(size_bitmap),
63
68
  }
64
69
  end
65
70
 
@@ -104,7 +109,7 @@ class Innodb::Xdes
104
109
 
105
110
  # Return the count of used pages (free bit is false) on this extent.
106
111
  def used_pages
107
- PAGES_PER_EXTENT - free_pages
112
+ @page.space.pages_per_extent - free_pages
108
113
  end
109
114
 
110
115
  # Return the address of the previous list pointer from the list node
metadata CHANGED
@@ -1,35 +1,48 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: innodb_ruby
3
- version: !ruby/object:Gem::Version
4
- version: 0.7.3
3
+ version: !ruby/object:Gem::Version
4
+ hash: 11
5
5
  prerelease:
6
+ segments:
7
+ - 0
8
+ - 7
9
+ - 4
10
+ version: 0.7.4
6
11
  platform: ruby
7
- authors:
12
+ authors:
8
13
  - Jeremy Cole
9
14
  autorequire:
10
15
  bindir: bin
11
16
  cert_chain: []
12
- date: 2012-12-16 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
17
+
18
+ date: 2012-12-20 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
15
21
  name: bindata
16
- requirement: &70099335724160 !ruby/object:Gem::Requirement
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
17
24
  none: false
18
- requirements:
19
- - - ! '>='
20
- - !ruby/object:Gem::Version
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 13
29
+ segments:
30
+ - 1
31
+ - 4
32
+ - 5
21
33
  version: 1.4.5
22
34
  type: :runtime
23
- prerelease: false
24
- version_requirements: *70099335724160
35
+ version_requirements: *id001
25
36
  description: Library for parsing InnoDB data files in Ruby
26
37
  email: jeremy@jcole.us
27
- executables:
38
+ executables:
28
39
  - innodb_log
29
40
  - innodb_space
30
41
  extensions: []
42
+
31
43
  extra_rdoc_files: []
32
- files:
44
+
45
+ files:
33
46
  - README.md
34
47
  - lib/innodb.rb
35
48
  - lib/innodb/cursor.rb
@@ -52,27 +65,37 @@ files:
52
65
  - bin/innodb_space
53
66
  homepage: http://jcole.us/
54
67
  licenses: []
68
+
55
69
  post_install_message:
56
70
  rdoc_options: []
57
- require_paths:
71
+
72
+ require_paths:
58
73
  - lib
59
- required_ruby_version: !ruby/object:Gem::Requirement
74
+ required_ruby_version: !ruby/object:Gem::Requirement
60
75
  none: false
61
- requirements:
62
- - - ! '>='
63
- - !ruby/object:Gem::Version
64
- version: '0'
65
- required_rubygems_version: !ruby/object:Gem::Requirement
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
66
84
  none: false
67
- requirements:
68
- - - ! '>='
69
- - !ruby/object:Gem::Version
70
- version: '0'
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ hash: 3
89
+ segments:
90
+ - 0
91
+ version: "0"
71
92
  requirements: []
93
+
72
94
  rubyforge_project:
73
- rubygems_version: 1.8.6
95
+ rubygems_version: 1.8.10
74
96
  signing_key:
75
97
  specification_version: 3
76
98
  summary: InnoDB data file parser
77
99
  test_files: []
100
+
78
101
  has_rdoc: