mctop 0.0.3 → 0.0.4
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/README.md +11 -4
- data/bin/mctop +1 -0
- data/lib/cmdline.rb +7 -1
- data/lib/sniffer.rb +5 -3
- data/lib/ui.rb +20 -8
- data/mctop.gemspec +1 -1
- metadata +2 -2
data/README.md
CHANGED
@@ -8,16 +8,18 @@ bandwidth.
|
|
8
8
|
You can read more detail about why this tool evovled over on our
|
9
9
|
[code as craft](http://codeascraft.etsy.com/2012/12/13/mctop-a-tool-for-analyzing-memcache-get-traffic) blog.
|
10
10
|
|
11
|
-
mctop depends on the [ruby-pcap](https://rubygems.org/gems/ruby-pcap) gem, if you don't have
|
12
|
-
this installed you'll need to ensure you have the development pcap libraries (libpcap-devel
|
11
|
+
mctop depends on the [ruby-pcap](https://rubygems.org/gems/ruby-pcap) gem, if you don't have
|
12
|
+
this installed you'll need to ensure you have the development pcap libraries (libpcap-devel
|
13
13
|
package on most linux distros) to build the native gem.
|
14
14
|
|
15
|
+

|
16
|
+
|
15
17
|
## How it works
|
16
18
|
|
17
19
|
mctop sniffs network traffic collecting memcache `VALUE` responses and calculates from
|
18
20
|
traffic statistics for each key seen. It currently reports on the following metrics per key:
|
19
21
|
|
20
|
-
* **calls** - the number of times the key has been called since mctop started
|
22
|
+
* **calls** - the number of times the key has been called since mctop started
|
21
23
|
* **objsize** - the size of the object stored for that key
|
22
24
|
* **req/sec** - the number of requests per second for the key
|
23
25
|
* **bw (kbps)** - the estimated netowrk bandwidth consumed by this key in kilobits-per-second
|
@@ -37,6 +39,7 @@ the quickest way to get it running is to:
|
|
37
39
|
|
38
40
|
Usage: mctop [options]
|
39
41
|
-i, --interface=NIC Network interface to sniff (required)
|
42
|
+
-p, --port=PORT Network port to sniff on (default 11211)
|
40
43
|
-d, --discard=THRESH Discard keys with request/sec rate below THRESH
|
41
44
|
-r, --refresh=MS Refresh the stats display every MS milliseconds
|
42
45
|
-h, --help Show usage info
|
@@ -61,8 +64,12 @@ The following details are displayed in the status bar
|
|
61
64
|
* `packets` - packets received and dropped by libpcap (% is percentage of packets dropped)
|
62
65
|
* `rt` - the time taken to sort and render the stats
|
63
66
|
|
67
|
+
## Changelog
|
68
|
+
|
69
|
+
* 2012-12-14 - Now compatible with Ruby 1.8.x (tested on 1.8.7-p371)
|
70
|
+
|
64
71
|
## Known issues / Gotchas
|
65
72
|
|
66
73
|
### ruby-pcap drops packets at high volume
|
67
|
-
from my testing the ruby-pcap native interface to libpcap struggles to keep up with high packet rates (in what we see on a production memcache instance) you can keep an eye on the packets recv/drop and loss percentage on the status bar at the bottom of the UI to get an idea of the packet
|
74
|
+
from my testing the ruby-pcap native interface to libpcap struggles to keep up with high packet rates (in what we see on a production memcache instance) you can keep an eye on the packets recv/drop and loss percentage on the status bar at the bottom of the UI to get an idea of the packet
|
68
75
|
|
data/bin/mctop
CHANGED
data/lib/cmdline.rb
CHANGED
@@ -10,15 +10,21 @@ class CmdLine
|
|
10
10
|
@config[:nic] = nic
|
11
11
|
end
|
12
12
|
|
13
|
+
opt.on('-p', '--port=PORT', 'Network port to sniff on (default 11211)') do |port|
|
14
|
+
@config[:port] = port
|
15
|
+
end
|
16
|
+
|
17
|
+
@config[:discard_thresh] = 0
|
13
18
|
opt.on '-d', '--discard=THRESH', Float, 'Discard keys with request/sec rate below THRESH' do |discard_thresh|
|
14
19
|
@config[:discard_thresh] = discard_thresh
|
15
20
|
end
|
16
21
|
|
22
|
+
@config[:refresh_rate] = 500
|
17
23
|
opt.on '-r', '--refresh=MS', Float, 'Refresh the stats display every MS milliseconds' do |refresh_rate|
|
18
24
|
@config[:refresh_rate] = refresh_rate
|
19
25
|
end
|
20
26
|
|
21
|
-
opt.on_tail '-h', '--help', 'Show usage info' do
|
27
|
+
opt.on_tail '-h', '--help', 'Show usage info' do
|
22
28
|
puts opts
|
23
29
|
exit
|
24
30
|
end
|
data/lib/sniffer.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
require 'pcap'
|
2
|
+
require 'thread'
|
2
3
|
|
3
|
-
class MemcacheSniffer
|
4
|
+
class MemcacheSniffer
|
4
5
|
attr_accessor :metrics, :semaphore
|
5
6
|
|
6
7
|
def initialize(config)
|
7
8
|
@source = config[:nic]
|
9
|
+
@port = config.has_key?(:port) ? config[:port] : 11211
|
8
10
|
|
9
11
|
@metrics = {}
|
10
12
|
@metrics[:calls] = {}
|
@@ -18,12 +20,12 @@ class MemcacheSniffer
|
|
18
20
|
|
19
21
|
def start
|
20
22
|
cap = Pcap::Capture.open_live(@source, 1500)
|
21
|
-
|
23
|
+
|
22
24
|
@metrics[:start_time] = Time.new.to_f
|
23
25
|
|
24
26
|
@done = false
|
25
27
|
|
26
|
-
cap.setfilter(
|
28
|
+
cap.setfilter("port #{@port}")
|
27
29
|
cap.loop do |packet|
|
28
30
|
@metrics[:stats] = cap.stats
|
29
31
|
|
data/lib/ui.rb
CHANGED
@@ -4,12 +4,14 @@ include Curses
|
|
4
4
|
|
5
5
|
class UI
|
6
6
|
def initialize(config)
|
7
|
+
@config = config
|
8
|
+
|
7
9
|
init_screen
|
8
10
|
cbreak
|
9
11
|
curs_set(0)
|
10
12
|
|
11
13
|
# set keyboard input timeout - sneaky way to manage refresh rate
|
12
|
-
Curses.timeout = config
|
14
|
+
Curses.timeout = @config[:refresh_rate]
|
13
15
|
|
14
16
|
if can_change_color?
|
15
17
|
start_color
|
@@ -22,9 +24,6 @@ class UI
|
|
22
24
|
@stat_col_width = 10
|
23
25
|
@key_col_width = 0
|
24
26
|
|
25
|
-
# we will delete any keys from the metrics table whose req/sec rate is below discard_rate
|
26
|
-
@discard_thresh = config.has_key?(:discard_thresh) ? config[:discard_thresh] : 0
|
27
|
-
|
28
27
|
@commands = {
|
29
28
|
'Q' => "quit",
|
30
29
|
'C' => "sort by calls",
|
@@ -63,7 +62,7 @@ class UI
|
|
63
62
|
|
64
63
|
# calculate packet loss ratio
|
65
64
|
if sniffer.metrics[:stats][:recv] > 0
|
66
|
-
loss = (sniffer.metrics[:stats][:drop].to_f / sniffer.metrics[:stats][:recv].to_f) * 100
|
65
|
+
loss = sprintf("%5.2f", (sniffer.metrics[:stats][:drop].to_f / sniffer.metrics[:stats][:recv].to_f) * 100)
|
67
66
|
else
|
68
67
|
loss = 0
|
69
68
|
end
|
@@ -74,7 +73,7 @@ class UI
|
|
74
73
|
header_summary = sprintf "%-28s %-14s %-30s",
|
75
74
|
"sort mode: #{sort_mode.to_s} (#{sort_order.to_s})",
|
76
75
|
"keys: #{sniffer.metrics[:calls].keys.count}",
|
77
|
-
"packets (recv/dropped): #{sniffer.metrics[:stats][:recv]} / #{sniffer.metrics[:stats][:drop]} (#{loss
|
76
|
+
"packets (recv/dropped): #{sniffer.metrics[:stats][:recv]} / #{sniffer.metrics[:stats][:drop]} (#{loss}%)"
|
78
77
|
addstr(sprintf "%-#{cols}s", header_summary)
|
79
78
|
|
80
79
|
# reset colours for main key display
|
@@ -95,7 +94,7 @@ class UI
|
|
95
94
|
# if req/sec is <= the discard threshold delete those keys from
|
96
95
|
# the metrics hash - this is a hack to manage the size of the
|
97
96
|
# metrics hash in high volume environments
|
98
|
-
if reqsec <= @discard_thresh
|
97
|
+
if reqsec <= @config[:discard_thresh]
|
99
98
|
sniffer.metrics[:calls].delete(k)
|
100
99
|
sniffer.metrics[:objsize].delete(k)
|
101
100
|
sniffer.metrics[:reqsec].delete(k)
|
@@ -151,7 +150,20 @@ class UI
|
|
151
150
|
end
|
152
151
|
|
153
152
|
def input_handler
|
154
|
-
getch
|
153
|
+
# Curses.getch has a bug in 1.8.x causing non-blocking
|
154
|
+
# calls to block reimplemented using IO.select
|
155
|
+
if RUBY_VERSION =~ /^1.8/
|
156
|
+
refresh_secs = @config[:refresh_rate].to_f / 1000
|
157
|
+
|
158
|
+
if IO.select([STDIN], nil, nil, refresh_secs)
|
159
|
+
c = getch
|
160
|
+
c.chr
|
161
|
+
else
|
162
|
+
nil
|
163
|
+
end
|
164
|
+
else
|
165
|
+
getch
|
166
|
+
end
|
155
167
|
end
|
156
168
|
|
157
169
|
def done
|
data/mctop.gemspec
CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |gem|
|
6
6
|
gem.name = "mctop"
|
7
|
-
gem.version = "0.0.
|
7
|
+
gem.version = "0.0.4"
|
8
8
|
gem.authors = ["Marcus Barczak"]
|
9
9
|
gem.email = ["marcus@etsy.com"]
|
10
10
|
gem.description = %q{mctop - a realtime memcache key analyzer}
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mctop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-12-
|
12
|
+
date: 2012-12-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: ruby-pcap
|