bitcask 0.2.0 → 0.2.1

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