mc_dump 0.0.0
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.
- checksums.yaml +15 -0
- data/lib/mc_dump/memcached/item.rb +29 -0
- data/lib/mc_dump/memcached/server.rb +20 -0
- data/lib/mc_dump/memcached/stat.rb +24 -0
- data/lib/mc_dump/rake/task.rb +20 -0
- data/lib/mc_dump/report.rb +31 -0
- data/lib/mc_dump/telnet/connection.rb +25 -0
- data/lib/mc_dump/telnet/session.rb +30 -0
- data/lib/mc_dump/version.rb +3 -0
- data/lib/mc_dump.rb +17 -0
- data/spec/lib/mc_dump/memcached/item_spec.rb +111 -0
- data/spec/lib/mc_dump/memcached/server_spec.rb +52 -0
- data/spec/lib/mc_dump/memcached/stat_spec.rb +118 -0
- data/spec/lib/mc_dump/rake/task_spec.rb +73 -0
- data/spec/lib/mc_dump/report_spec.rb +61 -0
- data/spec/lib/mc_dump/telnet/connection_spec.rb +88 -0
- data/spec/lib/mc_dump/telnet/session_spec.rb +109 -0
- data/spec/lib/mc_dump_spec.rb +43 -0
- data/spec/spec_helper.rb +18 -0
- metadata +169 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
OTYxNjg3MjBmMDI2N2M4NTQwNzY2MWQ0YzlhZDJlNTIxZmRkY2NjNA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
YzcyODBhODJmMjk0NTMyZWZjYTQ5MzQ3NzdkZTdkM2QzZTIxNWVjMQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NmI0Y2RlNjA3MTk2ZDdmOTVkMTQ5MWVkZDZkZTA5YTY1MDdmYzY0MTFlMDJi
|
10
|
+
MTI4NjVlMTRlNjlkYjk1MTczNzM1NTAzMjE5N2I0YTlmMDNhYzliYTU5ODA5
|
11
|
+
YzI4YzM2ZGQ0NzE2NzU5YzQ4MGQwYzc0OGE4MzZiZWU2OTBkYTY=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ZTRmMWUzYWIxNTk3NWJjZGJkZjc5ODE1YzVmYWEwNzBiNjA1OGU3YTVkNGE5
|
14
|
+
NGM5OGFmMDY4NDM5NGJmMmM5NzkzNTg5YTYxZjJkOTVmN2NkMjhmMjdjN2Rj
|
15
|
+
Y2Y4MjE3MDZmYWRlMmUxMWJmNTBmNzc4MzRhZjE1NDkxMGM5MmU=
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module McDump
|
2
|
+
module Memcached
|
3
|
+
|
4
|
+
class Item
|
5
|
+
|
6
|
+
def self.parse(dump, items_id)
|
7
|
+
dump.scan(/^ITEM (.+?) \[(\d+) b; (\d+) s\]$/).map do |item_data|
|
8
|
+
cache_key, size_in_bytes, expires_string = item_data
|
9
|
+
self.new(
|
10
|
+
items_id: items_id,
|
11
|
+
cache_key: cache_key,
|
12
|
+
expiration_time: Time.at(expires_string.to_i),
|
13
|
+
size_in_bytes: size_in_bytes.to_i
|
14
|
+
)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(args)
|
19
|
+
@data = args
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_h
|
23
|
+
@data.clone
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module McDump
|
2
|
+
module Memcached
|
3
|
+
|
4
|
+
class Server
|
5
|
+
|
6
|
+
def initialize(connection_args)
|
7
|
+
@connection_args = connection_args
|
8
|
+
end
|
9
|
+
|
10
|
+
def items
|
11
|
+
McDump::Telnet::Session.open(@connection_args) do |session|
|
12
|
+
item_stats = McDump::Memcached::Stat.parse(session.stats)
|
13
|
+
item_stats.map { |stat| stat.items(session) }.flatten
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module McDump
|
2
|
+
module Memcached
|
3
|
+
|
4
|
+
class Stat
|
5
|
+
|
6
|
+
def self.parse(dump)
|
7
|
+
dump.scan(/STAT items:(\d+):number (\d+)/).map do |data|
|
8
|
+
self.new(items_id: data[0], number: data[1])
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(args)
|
13
|
+
@items_id = args[:items_id]
|
14
|
+
@number = args[:number]
|
15
|
+
end
|
16
|
+
|
17
|
+
def items(telnet_session)
|
18
|
+
McDump::Memcached::Item.parse(telnet_session.items(@items_id, @number), @items_id)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module McDump
|
2
|
+
module Rake
|
3
|
+
|
4
|
+
class Task < ::Rake::TaskLib
|
5
|
+
|
6
|
+
def initialize(name, connection_args)
|
7
|
+
desc "Dumps contents of a memcached instance, supports arguments: host, port, timeout_in_seconds"
|
8
|
+
task(name) do
|
9
|
+
McDump.server({
|
10
|
+
host: ENV["host"],
|
11
|
+
port: ENV["port"],
|
12
|
+
timeout_in_seconds: ENV["timeout_in_seconds"]
|
13
|
+
}.merge(connection_args))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module McDump
|
2
|
+
|
3
|
+
class Report
|
4
|
+
|
5
|
+
def initialize(items)
|
6
|
+
@items = items
|
7
|
+
end
|
8
|
+
|
9
|
+
def dump(io=$stdout)
|
10
|
+
column_size_metadata = determine_column_size_metadata
|
11
|
+
column_names = column_size_metadata.keys
|
12
|
+
line_format = "|#{column_names.map { |name| " %#{column_size_metadata[name]}s " }.join("|")}|"
|
13
|
+
io.puts line_format % column_names
|
14
|
+
@items.each { |item| io.puts line_format % item.to_h.values_at(*column_names) }
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def determine_column_size_metadata
|
20
|
+
@items.reduce({}) do |size_metadata, item|
|
21
|
+
item.to_h.each do |data_key, data_value|
|
22
|
+
size_metadata[data_key] ||= data_key.to_s.length
|
23
|
+
size_metadata[data_key] = [ data_value.to_s.length, size_metadata[data_key] ].max
|
24
|
+
end
|
25
|
+
size_metadata
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module McDump
|
2
|
+
module Telnet
|
3
|
+
|
4
|
+
class Connection
|
5
|
+
|
6
|
+
def initialize(args)
|
7
|
+
@telnet = Net::Telnet::new(
|
8
|
+
"Host" => args[:host],
|
9
|
+
"Port" => args[:port],
|
10
|
+
"Timeout" => args[:timeout_in_seconds] || 5
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(string)
|
15
|
+
@telnet.cmd("String" => string, "Match" => /^END/)
|
16
|
+
end
|
17
|
+
|
18
|
+
def close
|
19
|
+
@telnet.close
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module McDump
|
2
|
+
module Telnet
|
3
|
+
|
4
|
+
class Session
|
5
|
+
|
6
|
+
def self.open(connection_args, &block)
|
7
|
+
session = self.new(connection_args)
|
8
|
+
block.call(session).tap { session.close }
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(connection_args)
|
12
|
+
@connection = McDump::Telnet::Connection.new(connection_args)
|
13
|
+
end
|
14
|
+
|
15
|
+
def stats
|
16
|
+
@connection.execute("stats items")
|
17
|
+
end
|
18
|
+
|
19
|
+
def items(id, number)
|
20
|
+
@connection.execute("stats cachedump #{id} #{number}")
|
21
|
+
end
|
22
|
+
|
23
|
+
def close
|
24
|
+
@connection.close
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
data/lib/mc_dump.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'net/telnet'
|
2
|
+
|
3
|
+
require_relative 'mc_dump/telnet/connection'
|
4
|
+
require_relative 'mc_dump/telnet/session'
|
5
|
+
require_relative 'mc_dump/memcached/item'
|
6
|
+
require_relative 'mc_dump/memcached/stat'
|
7
|
+
require_relative 'mc_dump/memcached/server'
|
8
|
+
require_relative 'mc_dump/report'
|
9
|
+
|
10
|
+
module McDump
|
11
|
+
|
12
|
+
def self.server(connection_args)
|
13
|
+
server = McDump::Memcached::Server.new(connection_args)
|
14
|
+
McDump::Report.new(server.items).dump
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
describe McDump::Memcached::Item do
|
2
|
+
|
3
|
+
let(:items_id) { "8" }
|
4
|
+
let(:cache_key) { SecureRandom.uuid }
|
5
|
+
let(:expiration_time) { "1447214865" }
|
6
|
+
let(:size_in_bytes) { 321 }
|
7
|
+
|
8
|
+
let(:args) do
|
9
|
+
{
|
10
|
+
items_id: items_id,
|
11
|
+
cache_key: cache_key,
|
12
|
+
expiration_time: expiration_time,
|
13
|
+
size_in_bytes: size_in_bytes
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:item) { described_class.new(args) }
|
18
|
+
|
19
|
+
describe "::parse" do
|
20
|
+
|
21
|
+
subject { described_class.parse(dump, items_id) }
|
22
|
+
|
23
|
+
context "when the dump contains many lines" do
|
24
|
+
|
25
|
+
let(:dump) do
|
26
|
+
<<-DUMP
|
27
|
+
ITEM namespace:fc7c3121-30c9-478c-9662-99f8b9df8931 [407 b; 1447214865 s]
|
28
|
+
ITEM namespace:e81bbb97-d679-4cea-80c2-787950256083 [392 b; 1447214866 s]
|
29
|
+
ITEM namespace:f5ed8536-1265-486a-afbf-73a26828a0ce [413 b; 1447214867 s]
|
30
|
+
DUMP
|
31
|
+
end
|
32
|
+
|
33
|
+
it "creates an item for each line containing the line cache keys" do
|
34
|
+
expected_cache_keys = %w{
|
35
|
+
namespace:fc7c3121-30c9-478c-9662-99f8b9df8931
|
36
|
+
namespace:e81bbb97-d679-4cea-80c2-787950256083
|
37
|
+
namespace:f5ed8536-1265-486a-afbf-73a26828a0ce
|
38
|
+
}
|
39
|
+
expected_cache_keys.each do |expected_cache_key|
|
40
|
+
expect(described_class).to receive(:new).with(hash_including(cache_key: expected_cache_key))
|
41
|
+
end
|
42
|
+
|
43
|
+
subject
|
44
|
+
end
|
45
|
+
|
46
|
+
it "creates an item for each line containing the line expiration times converted to Time" do
|
47
|
+
expected_expiration_times = %w{ 1447214865 1447214866 1447214867 }
|
48
|
+
expected_expiration_times.each do |expected_expiration_time|
|
49
|
+
expectation_as_time = Time.at(expected_expiration_time.to_i)
|
50
|
+
expect(described_class).to receive(:new).with(hash_including(expiration_time: expectation_as_time))
|
51
|
+
end
|
52
|
+
|
53
|
+
subject
|
54
|
+
end
|
55
|
+
|
56
|
+
it "creates an item for each line containing the line sizes in bytes" do
|
57
|
+
expected_sizes_in_bytes = [ 407, 392, 413 ]
|
58
|
+
expected_sizes_in_bytes.each do |expected_size_in_bytes|
|
59
|
+
expect(described_class).to receive(:new).with(hash_including(size_in_bytes: expected_size_in_bytes))
|
60
|
+
end
|
61
|
+
|
62
|
+
subject
|
63
|
+
end
|
64
|
+
|
65
|
+
it "creates an item for each line containing the provided items ID" do
|
66
|
+
expect(described_class).to receive(:new).with(hash_including(items_id: items_id)).exactly(3).times
|
67
|
+
|
68
|
+
subject
|
69
|
+
end
|
70
|
+
|
71
|
+
it "returns the created items" do
|
72
|
+
items = (1..3).map { instance_double(described_class) }
|
73
|
+
allow(described_class).to receive(:new).and_return(*items)
|
74
|
+
|
75
|
+
expect(subject).to eql(items)
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
context "when the dump is empty" do
|
81
|
+
|
82
|
+
let(:dump) { "" }
|
83
|
+
|
84
|
+
it "returns an empty array" do
|
85
|
+
expect(subject).to eql([])
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "#to_h" do
|
93
|
+
|
94
|
+
subject { item.to_h }
|
95
|
+
|
96
|
+
it "returns a hash containing the provided arguments" do
|
97
|
+
expect(subject).to eql(args)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "returns a copy of the provided arguments" do
|
101
|
+
original_cache_key = cache_key
|
102
|
+
|
103
|
+
hash = subject
|
104
|
+
args[:cache_key] = "modified cache key"
|
105
|
+
|
106
|
+
expect(hash[:cache_key]).to eql(original_cache_key)
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
describe McDump::Memcached::Server do
|
2
|
+
|
3
|
+
let(:connection_args) { { host: "some.host", port: 12345 } }
|
4
|
+
|
5
|
+
let(:server) { described_class.new(connection_args) }
|
6
|
+
|
7
|
+
describe "#items" do
|
8
|
+
|
9
|
+
let(:stats_dump) { "some stats dump" }
|
10
|
+
let(:items) { (1..3).map { (1..3).map { instance_double(McDump::Memcached::Item) } } }
|
11
|
+
let(:stats) { (1..3).map { instance_double(McDump::Memcached::Stat) } }
|
12
|
+
let(:telnet_session) { instance_double(McDump::Telnet::Session, stats: stats_dump) }
|
13
|
+
|
14
|
+
subject { server.items }
|
15
|
+
|
16
|
+
before(:example) do
|
17
|
+
allow(McDump::Telnet::Session).to receive(:open).and_yield(telnet_session)
|
18
|
+
allow(McDump::Memcached::Stat).to receive(:parse).and_return(stats)
|
19
|
+
stats.zip(items) { |stat, items| allow(stat).to receive(:items).and_return(items) }
|
20
|
+
end
|
21
|
+
|
22
|
+
it "opens a telnet session using the provided connection arguments" do
|
23
|
+
expect(McDump::Telnet::Session).to receive(:open).with(connection_args)
|
24
|
+
|
25
|
+
subject
|
26
|
+
end
|
27
|
+
|
28
|
+
it "retrieves the stats via the Memcached telnet session" do
|
29
|
+
expect(telnet_session).to receive(:stats)
|
30
|
+
|
31
|
+
subject
|
32
|
+
end
|
33
|
+
|
34
|
+
it "parses the stats" do
|
35
|
+
expect(McDump::Memcached::Stat).to receive(:parse).with(stats_dump)
|
36
|
+
|
37
|
+
subject
|
38
|
+
end
|
39
|
+
|
40
|
+
it "retrieves the items for each stat" do
|
41
|
+
stats.each { |stat| expect(stat).to receive(:items).with(telnet_session) }
|
42
|
+
|
43
|
+
subject
|
44
|
+
end
|
45
|
+
|
46
|
+
it "returns all the items" do
|
47
|
+
expect(subject).to eql(items.flatten)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
describe McDump::Memcached::Stat do
|
2
|
+
|
3
|
+
let(:items_id) { 8 }
|
4
|
+
let(:number) { 21 }
|
5
|
+
|
6
|
+
let(:stat) { described_class.new(items_id: items_id, number: number) }
|
7
|
+
|
8
|
+
describe "::parse" do
|
9
|
+
|
10
|
+
subject { described_class.parse(dump) }
|
11
|
+
|
12
|
+
context "when the dump contains many items" do
|
13
|
+
|
14
|
+
let(:dump) do
|
15
|
+
<<-DUMP
|
16
|
+
STAT items:9:number 19
|
17
|
+
STAT items:9:age 876543
|
18
|
+
STAT items:9:evicted 0
|
19
|
+
STAT items:9:evicted_nonzero 0
|
20
|
+
STAT items:9:evicted_time 0
|
21
|
+
STAT items:9:outofmemory 0
|
22
|
+
STAT items:9:tailrepairs 0
|
23
|
+
STAT items:9:reclaimed 3
|
24
|
+
STAT items:9:expired_unfetched 2
|
25
|
+
STAT items:9:evicted_unfetched 0
|
26
|
+
STAT items:9:crawler_reclaimed 0
|
27
|
+
STAT items:9:crawler_items_checked 0
|
28
|
+
STAT items:9:lrutail_reflocked 0
|
29
|
+
STAT items:10:number 20
|
30
|
+
STAT items:10:age 123456
|
31
|
+
STAT items:10:evicted 0
|
32
|
+
STAT items:10:evicted_nonzero 0
|
33
|
+
STAT items:10:evicted_time 0
|
34
|
+
STAT items:10:outofmemory 0
|
35
|
+
STAT items:10:tailrepairs 0
|
36
|
+
STAT items:10:reclaimed 6
|
37
|
+
STAT items:10:expired_unfetched 8
|
38
|
+
STAT items:10:evicted_unfetched 0
|
39
|
+
STAT items:10:crawler_reclaimed 0
|
40
|
+
STAT items:10:crawler_items_checked 0
|
41
|
+
STAT items:10:lrutail_reflocked 0
|
42
|
+
STAT items:11:number 21
|
43
|
+
STAT items:11:age 975319
|
44
|
+
STAT items:11:evicted 0
|
45
|
+
STAT items:11:evicted_nonzero 0
|
46
|
+
STAT items:11:evicted_time 0
|
47
|
+
STAT items:11:outofmemory 0
|
48
|
+
STAT items:11:tailrepairs 0
|
49
|
+
STAT items:11:reclaimed 1
|
50
|
+
STAT items:11:expired_unfetched 10
|
51
|
+
STAT items:11:evicted_unfetched 0
|
52
|
+
STAT items:11:crawler_reclaimed 0
|
53
|
+
STAT items:11:crawler_items_checked 0
|
54
|
+
STAT items:11:lrutail_reflocked 0
|
55
|
+
DUMP
|
56
|
+
end
|
57
|
+
|
58
|
+
it "creates a stat for each item entry containing the number of entries in the cache" do
|
59
|
+
stats_arg_expectations = [
|
60
|
+
{ items_id: "9", number: "19" }, { items_id: "10", number: "20" }, { items_id: "11", number: "21" }
|
61
|
+
]
|
62
|
+
stats_arg_expectations.each { |expectation| expect(described_class).to receive(:new).with(expectation) }
|
63
|
+
|
64
|
+
subject
|
65
|
+
end
|
66
|
+
|
67
|
+
it "returns the stats" do
|
68
|
+
stats = (1..3).map { instance_double(described_class) }
|
69
|
+
allow(described_class).to receive(:new).and_return(*stats)
|
70
|
+
|
71
|
+
expect(subject).to eql(stats)
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
context "when the dump is empty" do
|
77
|
+
|
78
|
+
let(:dump) { "" }
|
79
|
+
|
80
|
+
it "returns an empty array" do
|
81
|
+
expect(subject).to eql([])
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "#items" do
|
89
|
+
|
90
|
+
let(:items_dump) { "some items data" }
|
91
|
+
let(:telnet_session) { instance_double(McDump::Telnet::Session, items: items_dump) }
|
92
|
+
|
93
|
+
subject { stat.items(telnet_session) }
|
94
|
+
|
95
|
+
before(:example) { allow(telnet_session).to receive(:items).and_return(items_dump) }
|
96
|
+
|
97
|
+
it "determines the items in the memcached instance via the telnet session" do
|
98
|
+
expect(telnet_session).to receive(:items).with(items_id, number)
|
99
|
+
|
100
|
+
subject
|
101
|
+
end
|
102
|
+
|
103
|
+
it "parses the items returned via telnet" do
|
104
|
+
expect(McDump::Memcached::Item).to receive(:parse).with(items_dump, items_id)
|
105
|
+
|
106
|
+
subject
|
107
|
+
end
|
108
|
+
|
109
|
+
it "returns the parsed items" do
|
110
|
+
items = (1..3).map { instance_double(McDump::Memcached::Item) }
|
111
|
+
allow(McDump::Memcached::Item).to receive(:parse).and_return(items)
|
112
|
+
|
113
|
+
expect(subject).to eql(items)
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
describe McDump::Rake::Task do
|
2
|
+
|
3
|
+
describe "constructor" do
|
4
|
+
|
5
|
+
let(:name) { :some_dump_task }
|
6
|
+
let(:host) { "some.host" }
|
7
|
+
let(:port) { "12345" }
|
8
|
+
let(:timeout_in_seconds) { "10" }
|
9
|
+
let(:connection_args) { {} }
|
10
|
+
|
11
|
+
subject { described_class.new(name, connection_args) }
|
12
|
+
|
13
|
+
after(:example) { ::Rake::Task[name].clear }
|
14
|
+
|
15
|
+
it "creates a rake task with the provided name" do
|
16
|
+
subject
|
17
|
+
|
18
|
+
expect(::Rake::Task.task_defined?(name)).to be(true)
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "the created rake task" do
|
22
|
+
|
23
|
+
subject do
|
24
|
+
described_class.new(name, connection_args)
|
25
|
+
|
26
|
+
::Rake::Task[name].execute
|
27
|
+
end
|
28
|
+
|
29
|
+
context "when connection arguments are provided" do
|
30
|
+
|
31
|
+
let(:connection_args) { { host: host, port: port, timeout_in_seconds: timeout_in_seconds } }
|
32
|
+
|
33
|
+
it "dumps a server identified by the provided connection arguments" do
|
34
|
+
expect(McDump).to receive(:server).with(connection_args)
|
35
|
+
|
36
|
+
subject
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
context "when no connection arguments are provided" do
|
42
|
+
|
43
|
+
let(:connection_args) { {} }
|
44
|
+
|
45
|
+
before(:example) do
|
46
|
+
@original_env_host = ENV["host"]
|
47
|
+
@original_env_port = ENV["port"]
|
48
|
+
@original_env_timeout_in_seconds = ENV["timeout_in_seconds"]
|
49
|
+
|
50
|
+
ENV["host"] = host
|
51
|
+
ENV["port"] = port
|
52
|
+
ENV["timeout_in_seconds"] = timeout_in_seconds
|
53
|
+
end
|
54
|
+
|
55
|
+
after(:example) do
|
56
|
+
ENV["host"] = @original_env_host
|
57
|
+
ENV["port"] = @original_env_port
|
58
|
+
ENV["timeout_in_seconds"] = @original_env_timeout_in_seconds
|
59
|
+
end
|
60
|
+
|
61
|
+
it "dumps a server identified by environment variables host, port and timeout_in_seconds" do
|
62
|
+
expect(McDump).to receive(:server).with(host: host, port: port, timeout_in_seconds: timeout_in_seconds)
|
63
|
+
|
64
|
+
subject
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
describe McDump::Report do
|
2
|
+
|
3
|
+
let(:report) { described_class.new(items) }
|
4
|
+
|
5
|
+
describe "#dump" do
|
6
|
+
|
7
|
+
let(:io) { StringIO.new }
|
8
|
+
let(:output) { io.string }
|
9
|
+
|
10
|
+
subject { report.dump(io) }
|
11
|
+
|
12
|
+
context "when multiple items are provided" do
|
13
|
+
|
14
|
+
let(:base_time) { Time.new(2015, 11, 17, 12, 0, 0, "+10:00") }
|
15
|
+
|
16
|
+
let(:items) do
|
17
|
+
(1..3).map do |i|
|
18
|
+
McDump::Memcached::Item.new(
|
19
|
+
items_id: "#{i}",
|
20
|
+
cache_key: "some long cache key #{i}",
|
21
|
+
expiration_time: base_time + i,
|
22
|
+
size_in_bytes: i * 1024)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it "outputs a header row containing each column header spaced to display values" do
|
27
|
+
expected_header_row = "| items_id | cache_key | expiration_time | size_in_bytes |"
|
28
|
+
|
29
|
+
subject
|
30
|
+
|
31
|
+
expect(output).to start_with(expected_header_row)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "outputs a data row for each item" do
|
35
|
+
expected_data_rows =
|
36
|
+
"| 1 | some long cache key 1 | 2015-11-17 12:00:01 +1000 | 1024 |\n" +
|
37
|
+
"| 2 | some long cache key 2 | 2015-11-17 12:00:02 +1000 | 2048 |\n" +
|
38
|
+
"| 3 | some long cache key 3 | 2015-11-17 12:00:03 +1000 | 3072 |"
|
39
|
+
|
40
|
+
subject
|
41
|
+
|
42
|
+
expect(output).to include(expected_data_rows)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
context "when no items are provided" do
|
48
|
+
|
49
|
+
let(:items) { [] }
|
50
|
+
|
51
|
+
it "outputs an empty header row" do
|
52
|
+
subject
|
53
|
+
|
54
|
+
expect(output).to eql("||\n")
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
describe McDump::Telnet::Connection do
|
2
|
+
|
3
|
+
let(:host) { "some.host" }
|
4
|
+
let(:port) { 12345 }
|
5
|
+
let(:additional_args) { {} }
|
6
|
+
let(:args) { { host: host, port: port }.merge(additional_args) }
|
7
|
+
let(:telnet) { instance_double(Net::Telnet) }
|
8
|
+
|
9
|
+
let(:connection) { described_class.new(args) }
|
10
|
+
|
11
|
+
before(:example) { allow(Net::Telnet).to receive(:new).and_return(telnet) }
|
12
|
+
|
13
|
+
describe "constructor" do
|
14
|
+
|
15
|
+
subject { connection }
|
16
|
+
|
17
|
+
it "creates a telnet instance with the provided host and port" do
|
18
|
+
expect(Net::Telnet).to receive(:new).with(hash_including("Host" => host, "Port" => port))
|
19
|
+
|
20
|
+
subject
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when a connection timeout is provided" do
|
24
|
+
|
25
|
+
let(:timeout_in_seconds) { 8 }
|
26
|
+
let(:additional_args) { { timeout_in_seconds: timeout_in_seconds } }
|
27
|
+
|
28
|
+
it "creates a telnet instance with the provided timeout in seconds" do
|
29
|
+
expect(Net::Telnet).to receive(:new).with(hash_including("Timeout" => timeout_in_seconds))
|
30
|
+
|
31
|
+
subject
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when a connection timeout is not provided" do
|
37
|
+
|
38
|
+
let(:additional_args) { {} }
|
39
|
+
|
40
|
+
it "defaults the timeout to 5 seconds" do
|
41
|
+
expect(Net::Telnet).to receive(:new).with(hash_including("Timeout" => 5))
|
42
|
+
|
43
|
+
subject
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#execute" do
|
51
|
+
|
52
|
+
let(:command) { "some command" }
|
53
|
+
|
54
|
+
subject { connection.execute(command) }
|
55
|
+
|
56
|
+
it "sends the provided command string to the underlying telnet instance" do
|
57
|
+
expect(telnet).to receive(:cmd).with(hash_including("String" => command))
|
58
|
+
|
59
|
+
subject
|
60
|
+
end
|
61
|
+
|
62
|
+
it "concludes awaiting output when END is observed in the stream" do
|
63
|
+
expect(telnet).to receive(:cmd).with(hash_including("Match" => /^END/))
|
64
|
+
|
65
|
+
subject
|
66
|
+
end
|
67
|
+
|
68
|
+
it "returns the result of the command" do
|
69
|
+
allow(telnet).to receive(:cmd).and_return("some command result")
|
70
|
+
|
71
|
+
expect(subject).to eql("some command result")
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "#close" do
|
77
|
+
|
78
|
+
subject { connection.close }
|
79
|
+
|
80
|
+
it "closes the underlying telnet instance" do
|
81
|
+
expect(telnet).to receive(:close)
|
82
|
+
|
83
|
+
subject
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
describe McDump::Telnet::Session do
|
2
|
+
|
3
|
+
let(:connection_args) { { host: "some.host", port: 12345 } }
|
4
|
+
let(:connection) { instance_double(McDump::Telnet::Connection) }
|
5
|
+
|
6
|
+
let(:session) { described_class.new(connection_args) }
|
7
|
+
|
8
|
+
before(:example) { allow(McDump::Telnet::Connection).to receive(:new).and_return(connection) }
|
9
|
+
|
10
|
+
describe "::open" do
|
11
|
+
|
12
|
+
let(:open_block_result) { "some open block result"}
|
13
|
+
let(:open_block) { lambda { |session| open_block_result } }
|
14
|
+
let(:session) { instance_double(described_class, close: nil) }
|
15
|
+
|
16
|
+
subject { described_class.open(connection_args, &open_block) }
|
17
|
+
|
18
|
+
before(:example) { allow(described_class).to receive(:new).and_return(session) }
|
19
|
+
|
20
|
+
it "creates a session with the provided connection settings" do
|
21
|
+
expect(described_class).to receive(:new).with(connection_args)
|
22
|
+
|
23
|
+
subject
|
24
|
+
end
|
25
|
+
|
26
|
+
it "invokes the provided block with the created session" do
|
27
|
+
expect(open_block).to receive(:call).with(session)
|
28
|
+
|
29
|
+
subject
|
30
|
+
end
|
31
|
+
|
32
|
+
it "closes the created session" do
|
33
|
+
expect(session).to receive(:close)
|
34
|
+
|
35
|
+
subject
|
36
|
+
end
|
37
|
+
|
38
|
+
it "returns the result of the block" do
|
39
|
+
expect(subject).to eql(open_block_result)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "constructor" do
|
45
|
+
|
46
|
+
subject { session }
|
47
|
+
|
48
|
+
it "opens a connection using the provided connection settings" do
|
49
|
+
expect(McDump::Telnet::Connection).to receive(:new).with(connection_args)
|
50
|
+
|
51
|
+
subject
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "#stats" do
|
57
|
+
|
58
|
+
subject { session.stats }
|
59
|
+
|
60
|
+
it "executes a command on the connection to retrieve the stats for items" do
|
61
|
+
expect(connection).to receive(:execute).with("stats items")
|
62
|
+
|
63
|
+
subject
|
64
|
+
end
|
65
|
+
|
66
|
+
it "returns the commands response" do
|
67
|
+
response = (1..3).map { |i| "STAT line #{i}" }
|
68
|
+
allow(connection).to receive(:execute).and_return(response)
|
69
|
+
|
70
|
+
expect(subject).to eql(response)
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "#items" do
|
76
|
+
|
77
|
+
let(:id) { 12 }
|
78
|
+
let(:number) { 987 }
|
79
|
+
|
80
|
+
subject { session.items(id, number) }
|
81
|
+
|
82
|
+
it "executes a command on the connection showing the number of items matching the id" do
|
83
|
+
expect(connection).to receive(:execute).with("stats cachedump #{id} #{number}")
|
84
|
+
|
85
|
+
subject
|
86
|
+
end
|
87
|
+
|
88
|
+
it "returns the commands response" do
|
89
|
+
response = (1..3).map { |i| "ITEM line #{i}" }
|
90
|
+
allow(connection).to receive(:execute).and_return(response)
|
91
|
+
|
92
|
+
expect(subject).to eql(response)
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "#close" do
|
98
|
+
|
99
|
+
subject { session.close }
|
100
|
+
|
101
|
+
it "closes the telnet connection that was made when the session was created" do
|
102
|
+
expect(connection).to receive(:close)
|
103
|
+
|
104
|
+
subject
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
describe McDump do
|
2
|
+
|
3
|
+
describe "::server" do
|
4
|
+
|
5
|
+
let(:connection_args) { { host: "some.host", port: 12345 } }
|
6
|
+
let(:items) { (1..3).map { instance_double(McDump::Memcached::Item) } }
|
7
|
+
let(:server) { instance_double(McDump::Memcached::Server, items: items) }
|
8
|
+
let(:report) { instance_double(McDump::Report, dump: nil) }
|
9
|
+
|
10
|
+
subject { described_class.server(connection_args) }
|
11
|
+
|
12
|
+
before(:example) do
|
13
|
+
allow(McDump::Memcached::Server).to receive(:new).and_return(server)
|
14
|
+
allow(McDump::Report).to receive(:new).and_return(report)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "creates a server" do
|
18
|
+
expect(McDump::Memcached::Server).to receive(:new).with(connection_args)
|
19
|
+
|
20
|
+
subject
|
21
|
+
end
|
22
|
+
|
23
|
+
it "retrieves all items from the server" do
|
24
|
+
expect(server).to receive(:items)
|
25
|
+
|
26
|
+
subject
|
27
|
+
end
|
28
|
+
|
29
|
+
it "creates a report containing the items" do
|
30
|
+
expect(McDump::Report).to receive(:new).with(items)
|
31
|
+
|
32
|
+
subject
|
33
|
+
end
|
34
|
+
|
35
|
+
it "dumps the contents of the report" do
|
36
|
+
expect(report).to receive(:dump)
|
37
|
+
|
38
|
+
subject
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler.require(:development)
|
3
|
+
|
4
|
+
CodeClimate::TestReporter.start
|
5
|
+
|
6
|
+
SimpleCov.start do
|
7
|
+
coverage_dir "tmp/coverage"
|
8
|
+
|
9
|
+
add_filter "/spec/"
|
10
|
+
|
11
|
+
minimum_coverage 100
|
12
|
+
refuse_coverage_drop
|
13
|
+
end if ENV["coverage"]
|
14
|
+
|
15
|
+
require 'rake/tasklib'
|
16
|
+
|
17
|
+
require_relative '../lib/mc_dump'
|
18
|
+
require_relative '../lib/mc_dump/rake/task'
|
metadata
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mc_dump
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matthew Ueckerman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-11-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '10.4'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '10.4'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: travis-lint
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: metric_fu
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '4.12'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '4.12'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.4'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.4'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: coderay
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.1'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.1'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: simplecov
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.10'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.10'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: codeclimate-test-reporter
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.4'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.4'
|
111
|
+
description: Dumps key values pairs within a memcached instance. Useful for verification
|
112
|
+
of cached content.
|
113
|
+
email: matthew.ueckerman@myob.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- ./lib/mc_dump.rb
|
119
|
+
- ./lib/mc_dump/memcached/item.rb
|
120
|
+
- ./lib/mc_dump/memcached/server.rb
|
121
|
+
- ./lib/mc_dump/memcached/stat.rb
|
122
|
+
- ./lib/mc_dump/rake/task.rb
|
123
|
+
- ./lib/mc_dump/report.rb
|
124
|
+
- ./lib/mc_dump/telnet/connection.rb
|
125
|
+
- ./lib/mc_dump/telnet/session.rb
|
126
|
+
- ./lib/mc_dump/version.rb
|
127
|
+
- ./spec/lib/mc_dump/memcached/item_spec.rb
|
128
|
+
- ./spec/lib/mc_dump/memcached/server_spec.rb
|
129
|
+
- ./spec/lib/mc_dump/memcached/stat_spec.rb
|
130
|
+
- ./spec/lib/mc_dump/rake/task_spec.rb
|
131
|
+
- ./spec/lib/mc_dump/report_spec.rb
|
132
|
+
- ./spec/lib/mc_dump/telnet/connection_spec.rb
|
133
|
+
- ./spec/lib/mc_dump/telnet/session_spec.rb
|
134
|
+
- ./spec/lib/mc_dump_spec.rb
|
135
|
+
- ./spec/spec_helper.rb
|
136
|
+
homepage: http://github.com/MYOB-Technology/mc_dump
|
137
|
+
licenses:
|
138
|
+
- MIT
|
139
|
+
metadata: {}
|
140
|
+
post_install_message:
|
141
|
+
rdoc_options: []
|
142
|
+
require_paths:
|
143
|
+
- lib
|
144
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - ! '>='
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: 1.9.3
|
149
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - ! '>='
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
requirements: []
|
155
|
+
rubyforge_project:
|
156
|
+
rubygems_version: 2.4.8
|
157
|
+
signing_key:
|
158
|
+
specification_version: 4
|
159
|
+
summary: Dumps the contents of a memcached instance
|
160
|
+
test_files:
|
161
|
+
- ./spec/lib/mc_dump/memcached/item_spec.rb
|
162
|
+
- ./spec/lib/mc_dump/memcached/server_spec.rb
|
163
|
+
- ./spec/lib/mc_dump/memcached/stat_spec.rb
|
164
|
+
- ./spec/lib/mc_dump/rake/task_spec.rb
|
165
|
+
- ./spec/lib/mc_dump/report_spec.rb
|
166
|
+
- ./spec/lib/mc_dump/telnet/connection_spec.rb
|
167
|
+
- ./spec/lib/mc_dump/telnet/session_spec.rb
|
168
|
+
- ./spec/lib/mc_dump_spec.rb
|
169
|
+
- ./spec/spec_helper.rb
|