innodb_ruby 0.7.3 → 0.7.4

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