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.
- data/bin/bitcask +370 -0
- data/lib/bitcask/version.rb +1 -1
- metadata +5 -4
data/bin/bitcask
ADDED
@@ -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
|
data/lib/bitcask/version.rb
CHANGED
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 2
|
8
|
-
-
|
9
|
-
version: 0.2.
|
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
|