bitcask 0.2.0 → 0.2.1

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.
Files changed (3) hide show
  1. data/bin/bitcask +370 -0
  2. data/lib/bitcask/version.rb +1 -1
  3. metadata +5 -4
@@ -0,0 +1,370 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path("#{File.dirname(__FILE__)}/../lib/bitcask")
4
+ require 'rubygems'
5
+ require 'trollop'
6
+ require 'ostruct'
7
+
8
+ class Bitcask::Tool
9
+ K = Struct.new :key
10
+ KV = Struct.new :key, :value
11
+
12
+ def run
13
+ subcommands = %w(count last get dump)
14
+
15
+ opts = Trollop::options do
16
+ banner <<EOF
17
+ Bitcask utility.
18
+
19
+ bitcask <bitcask_dir> [options] <command> [subcommand options]
20
+
21
+ Commands: #{subcommands.join(', ')}.
22
+
23
+ Options:
24
+ EOF
25
+
26
+ opt :key, "key", :type => :string
27
+ opt :bucket, "bucket", :type => :string
28
+ opt :value, "value", :type => :string
29
+ opt :color, "Colorize output"
30
+ opt :no_riak, "Do not interpret buckets and keys as Riak does"
31
+ opt :no_values, "Do not process values"
32
+ opt :no_regexp, "Do not interpret patterns as regular expressions."
33
+ opt :format, "Output format string.
34
+ %b: Bucket
35
+ %k: Key
36
+ %v: Value
37
+ ", :type => :string
38
+ opt :verbose_values, "Display full riak values"
39
+ stop_on subcommands
40
+ end
41
+
42
+ # Set no_x options.
43
+ opts = opts.inject({}) do |opts, pair|
44
+ k,v = pair
45
+ if k.to_s =~ /^no_(\w+)/
46
+ opts[$1.to_sym] = ! v
47
+ else
48
+ opts[k] = v
49
+ end
50
+
51
+ opts
52
+ end
53
+
54
+ # Load bitcasks
55
+ bitcasks = bitcasks(ARGV.shift)
56
+ if bitcasks.empty?
57
+ Trollop::die "No bitcasks"
58
+ end
59
+
60
+ # Load libraries
61
+ require 'bert' if opts[:riak]
62
+ require 'ansi' if opts[:color]
63
+ require 'pp' if opts[:verbose_values]
64
+
65
+ # Subcommand options
66
+ command = ARGV.shift
67
+ opts.merge!(
68
+ case command
69
+ when 'all'
70
+ {}
71
+ when 'count'
72
+ {}
73
+ when 'dump'
74
+ {}
75
+ when 'get'
76
+ if opts[:riak]
77
+ {
78
+ :bucket => ARGV.shift,
79
+ :key => ARGV.shift
80
+ }
81
+ else
82
+ {:key => ARGV.shift}
83
+ end
84
+ when 'last'
85
+ Trollop::options do
86
+ opt :limit, 'results to return', :default => 1
87
+ end
88
+ else
89
+ Trollop::die "unknown command #{command.inspect}"
90
+ end
91
+ )
92
+
93
+ # Run commands
94
+ send command, bitcasks, opts
95
+ end
96
+
97
+ # Returns all the data in a set of bitcasks.
98
+ # This differs from dump in that it loads the keydir, so it shows
99
+ # only the most recent values.
100
+ def all(bitcasks, opts)
101
+ f = filter opts
102
+
103
+ bitcasks.each do |bitcask|
104
+ bitcask.load
105
+
106
+ if opts[:value]
107
+ bitcask.keydir.each_key do |key|
108
+ if f[key, nil]
109
+ # Get the value
110
+ value = bitcask[key]
111
+ if f[key, value]
112
+ out KV.new(key, value), opts
113
+ end
114
+ end
115
+ end
116
+ else
117
+ # Use the keydir alone.
118
+ bitcask.keydir.each_key do |key|
119
+ if f[key, nil]
120
+ out K.new(key), opts
121
+ end
122
+ end
123
+ end
124
+
125
+ # Let GC do its thing.
126
+ bitcask.keydir = nil
127
+ end
128
+ end
129
+
130
+ # Returns all bitcasks in directory.
131
+ def bitcasks(directory)
132
+ return [] unless File.directory? directory
133
+
134
+ entries = Dir.entries(directory).map do |f|
135
+ next if f == '.' or f == '..'
136
+ File.join(directory, f)
137
+ end.compact
138
+
139
+ if entries.any? { |f| File.file? f and f =~ /\.data$/ }
140
+ return [Bitcask.new(directory)]
141
+ end
142
+
143
+ entries.map do |e|
144
+ bitcasks e
145
+ end.flatten
146
+ end
147
+
148
+ # Colorizes a string.
149
+ def color(color, str, opts)
150
+ if opts[:color]
151
+ ANSI.style(color) { str }
152
+ else
153
+ str
154
+ end
155
+ end
156
+
157
+ # Count all the keys matching opts
158
+ def count(bitcasks, opts)
159
+ f = filter opts
160
+
161
+ require 'set'
162
+ keys = Set.new
163
+ bitcasks.each do |bitcask|
164
+ bitcask.data_files.each do |d|
165
+ if h = d.hint_file
166
+ h.each do |e|
167
+ if f[e.key, nil]
168
+ keys << e.key
169
+ end
170
+ end
171
+ else
172
+ d.each do |e|
173
+ if f[e.key, e.value]
174
+ keys << e.key
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
180
+
181
+ puts keys.size
182
+ end
183
+
184
+ # Dump every record in each bitcask, cronologically.
185
+ def dump(bitcasks, opts)
186
+ f = filter opts
187
+
188
+ bitcasks.each do |bitcask|
189
+ bitcask.data_files.each do |d|
190
+ if opts[:values] or not d.hint_file
191
+ # Iterate over data files.
192
+ d.each do |e|
193
+ if f[e.key, e.value]
194
+ out e, opts
195
+ end
196
+ end
197
+ else
198
+ # Use hint files where possible.
199
+ d.hint_file.each do |e|
200
+ if f[e.key, nil]
201
+ out e, opts
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end
208
+
209
+ # Returns a proc to match bitcask entries.
210
+ def filter(opts)
211
+ pkey = pattern opts[:key], opts
212
+ pbucket = pattern opts[:bucket], opts
213
+ pvalue = pattern opts[:value], opts
214
+
215
+ # Trivial case: don't filter anything.
216
+ if pkey.nil? and pbucket.nil? and pvalue.nil?
217
+ return lambda { |k, v| true }
218
+ end
219
+
220
+ if opts[:riak]
221
+ # Taking advantage of the fact that erlang tuples are encoded as
222
+ # tuple, Size, string, Size, Data, string, Size, Data
223
+ bucket_part = pbucket.kind_of?(String) ? Regexp.escape(pbucket) : pbucket
224
+ key_part = pkey.kind_of?(String) ? Regexp.escape(pkey) : pkey
225
+ p_raw_key = /#{bucket_part}.+#{key_part}/
226
+
227
+ lambda do |key, value|
228
+ if p_raw_key === key or (pkey.nil? and pvalue.nil? and pvalue)
229
+
230
+ # Confirm match.
231
+ bucket, key = BERT.decode key
232
+
233
+ (not pkey or pkey === key) and
234
+ (not pbucket or pbucket === bucket) and
235
+ (not pvalue or !value or pvalue === value)
236
+ end
237
+ end
238
+ else
239
+ lambda do |key, value|
240
+ (not pkey or pkey === key) and
241
+ (not pvalue or !value or pvalue === value)
242
+ end
243
+ end
244
+ end
245
+
246
+ # Get a specific value.
247
+ def get(bitcasks, opts)
248
+ if opts[:riak]
249
+ t = BERT::Tuple.new
250
+ t << opts[:bucket]
251
+ t << opts[:key]
252
+ key = BERT::encode t
253
+ else
254
+ key = opts[:key]
255
+ end
256
+
257
+ key.force_encoding('BINARY')
258
+ value = bitcasks.inject(nil) do |value, bitcask|
259
+ value or begin
260
+ bitcask.load
261
+ bitcask[key]
262
+ end
263
+ end
264
+
265
+ if value
266
+ out OpenStruct.new('key' => key, 'value' => value), opts
267
+ end
268
+ end
269
+
270
+ # Dump the last few items from some bitcasks.
271
+ def last(bitcasks, opts)
272
+ f = filter opts
273
+
274
+ buffers = bitcasks.map do |bitcask|
275
+ buffer = Array.new opts[:limit]
276
+
277
+ bitcask.data_files.each do |d|
278
+ # Get hint file
279
+ unless h = d.hint_file
280
+ warn "No hint file for #{d.inspect}"
281
+ next
282
+ end
283
+
284
+ # Run over hintfile
285
+ h.each do |e|
286
+ if f[e.key, nil]
287
+ buffer.shift
288
+ buffer << [d, e]
289
+ end
290
+ end
291
+ end
292
+
293
+ buffer.compact
294
+ end
295
+
296
+ # Merge buffers.
297
+ buffer = buffers.flatten(1).sort do |a, b|
298
+ a[1].tstamp <=> b[1].tstamp
299
+ end.last(opts[:limit])
300
+
301
+ # Display buffers.
302
+ buffer.map do |d, e|
303
+ if opts[:values]
304
+ d[e.value_pos, e.value_sz]
305
+ else
306
+ e
307
+ end
308
+ end.each do |e|
309
+ out e, opts
310
+ end
311
+ end
312
+
313
+ # Returns an object which uses === for comparison
314
+ def pattern(string, opts = {})
315
+ return nil unless string
316
+
317
+ if opts[:regexp] and string =~ /[^A-Za-z0-0_]/
318
+ /#{string}/
319
+ else
320
+ string
321
+ end
322
+ end
323
+
324
+ # Output an entry.
325
+ def out(entry, opts = {})
326
+ # Value
327
+ if entry.respond_to? :value and entry.value == Bitcask::TOMBSTONE
328
+ svalue = color :red, entry.value, opts
329
+ end
330
+
331
+ if opts[:riak]
332
+ # Riak
333
+ format = opts[:format] || "%b/%k\n%v"
334
+
335
+ bucket, key = BERT.decode entry.key
336
+
337
+ sbucket = color :green, bucket, opts
338
+ skey = color :green, key, opts
339
+
340
+ svalue ||= begin
341
+ value = BERT.decode entry.value
342
+ if opts[:verbose_values]
343
+ svalue = PP.pp(value, '')
344
+ else
345
+ svalue = value.last.to_s
346
+ end
347
+ end
348
+ else
349
+ # Not riak
350
+ format = opts[:format] || "%k\n%v"
351
+ skey = color :green, entry.key, opts
352
+ svalue ||= entry.value
353
+ sbucket = ''
354
+ end
355
+
356
+ # Format
357
+ s = format.gsub('%k', skey).gsub('%v', svalue).gsub('%b', sbucket)
358
+
359
+ puts s
360
+ s
361
+ end
362
+
363
+ def warn(s)
364
+ puts s
365
+ end
366
+ end
367
+
368
+ if $0 == __FILE__
369
+ Bitcask::Tool.new.run
370
+ end
@@ -1,3 +1,3 @@
1
1
  class Bitcask
2
- VERSION = '0.2.0'
2
+ VERSION = '0.2.1'
3
3
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 2
8
- - 0
9
- version: 0.2.0
8
+ - 1
9
+ version: 0.2.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Kyle Kingsbury
@@ -20,13 +20,14 @@ dependencies: []
20
20
 
21
21
  description:
22
22
  email: aphyr@aphyr.com
23
- executables: []
24
-
23
+ executables:
24
+ - bitcask
25
25
  extensions: []
26
26
 
27
27
  extra_rdoc_files: []
28
28
 
29
29
  files:
30
+ - bin/bitcask
30
31
  - lib/bitcask.rb
31
32
  - lib/bitcask/data_file.rb
32
33
  - lib/bitcask/version.rb