mctop 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +68 -0
- data/Rakefile +1 -0
- data/bin/mctop +71 -0
- data/lib/cmdline.rb +51 -0
- data/lib/sniffer.rb +55 -0
- data/lib/ui.rb +161 -0
- data/mctop.gemspec +19 -0
- metadata +74 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Etsy
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# mctop
|
2
|
+
|
3
|
+
Inspired by "top", mctop passively sniffs the network traffic passing in and out of a
|
4
|
+
server's network interface and tracks the keys responding to memcache get commands. The output
|
5
|
+
is presented on the terminal and allows sorting by total calls, requests/sec and
|
6
|
+
bandwidth.
|
7
|
+
|
8
|
+
You can read more detail about why this tool evovled over on our
|
9
|
+
[code as craft](http://codeascraft.etsy.com/2012/12/13/mctop-a-tool-for-analyzing-memcache-get-traffic) blog.
|
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
|
13
|
+
package on most linux distros) to build the native gem.
|
14
|
+
|
15
|
+
## How it works
|
16
|
+
|
17
|
+
mctop sniffs network traffic collecting memcache `VALUE` responses and calculates from
|
18
|
+
traffic statistics for each key seen. It currently reports on the following metrics per key:
|
19
|
+
|
20
|
+
* **calls** - the number of times the key has been called since mctop started
|
21
|
+
* **objsize** - the size of the object stored for that key
|
22
|
+
* **req/sec** - the number of requests per second for the key
|
23
|
+
* **bw (kbps)** - the estimated netowrk bandwidth consumed by this key in kilobits-per-second
|
24
|
+
|
25
|
+
## Getting it running
|
26
|
+
|
27
|
+
the quickest way to get it running is to:
|
28
|
+
|
29
|
+
* ensure you have libpcap-devel installed
|
30
|
+
* git clone this repo
|
31
|
+
* in the top level directory of this repo `bundle install` (this will install the deps)
|
32
|
+
* then either:
|
33
|
+
* install it locally `rake install`; or
|
34
|
+
* run it from the repo (good for hacking) `sudo ./bin/mctop --help`
|
35
|
+
|
36
|
+
## Command line options
|
37
|
+
|
38
|
+
Usage: mctop [options]
|
39
|
+
-i, --interface=NIC Network interface to sniff (required)
|
40
|
+
-d, --discard=THRESH Discard keys with request/sec rate below THRESH
|
41
|
+
-r, --refresh=MS Refresh the stats display every MS milliseconds
|
42
|
+
-h, --help Show usage info
|
43
|
+
|
44
|
+
## User interface commands
|
45
|
+
|
46
|
+
The following key commands are available in the console UI:
|
47
|
+
|
48
|
+
* `C` - sort by number of calls
|
49
|
+
* `S` - sort by object size
|
50
|
+
* `R` - sort by requests/sec
|
51
|
+
* `B` - sort by bandwidth
|
52
|
+
* `T` - toggle sorting by ascending / descending order
|
53
|
+
* `Q` - quits
|
54
|
+
|
55
|
+
## Status bar
|
56
|
+
|
57
|
+
The following details are displayed in the status bar
|
58
|
+
|
59
|
+
* `sort mode` - the current sort mode and ordering
|
60
|
+
* `keys` - total number of keys in the metrics table
|
61
|
+
* `packets` - packets received and dropped by libpcap (% is percentage of packets dropped)
|
62
|
+
* `rt` - the time taken to sort and render the stats
|
63
|
+
|
64
|
+
## Known issues / Gotchas
|
65
|
+
|
66
|
+
### 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
|
68
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/mctop
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# mctop - A command line memcached traffic analyzer
|
4
|
+
#
|
5
|
+
# Author:: Marcus Barczak (<marcus@etsy.com>)
|
6
|
+
|
7
|
+
$:.unshift File.join(File.dirname(__FILE__),'..','lib')
|
8
|
+
|
9
|
+
require 'cmdline'
|
10
|
+
require 'sniffer'
|
11
|
+
require 'ui'
|
12
|
+
|
13
|
+
@config = CmdLine.parse(ARGV)
|
14
|
+
|
15
|
+
# instantiate a sniffer and user interface object
|
16
|
+
sniffer = MemcacheSniffer.new(@config)
|
17
|
+
ui = UI.new(@config)
|
18
|
+
|
19
|
+
# set default display options
|
20
|
+
sort_mode = :reqsec
|
21
|
+
sort_order = :desc
|
22
|
+
done = false
|
23
|
+
|
24
|
+
# trap most of the typical signals
|
25
|
+
%w[ INT QUIT HUP KILL ].each do |sig|
|
26
|
+
Signal.trap(sig) do
|
27
|
+
puts "** Caught signal #{sig} - exiting"
|
28
|
+
done = true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# kick the sniffer thread off
|
33
|
+
sniff_thread = Thread.new { sniffer.start }
|
34
|
+
|
35
|
+
# main loop
|
36
|
+
until done do
|
37
|
+
ui.header
|
38
|
+
ui.footer
|
39
|
+
ui.render_stats(sniffer, sort_mode, sort_order)
|
40
|
+
refresh
|
41
|
+
|
42
|
+
key = ui.input_handler
|
43
|
+
case key
|
44
|
+
when /[Qq]/
|
45
|
+
done = true
|
46
|
+
when /[Cc]/
|
47
|
+
sort_mode = :calls
|
48
|
+
when /[Ss]/
|
49
|
+
sort_mode = :objsize
|
50
|
+
when /[Rr]/
|
51
|
+
sort_mode = :reqsec
|
52
|
+
when /[Bb]/
|
53
|
+
sort_mode = :bw
|
54
|
+
when /[Tt]/
|
55
|
+
if sort_order == :desc
|
56
|
+
sort_order = :asc
|
57
|
+
else
|
58
|
+
sort_order = :desc
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
## cleanup
|
64
|
+
ui.done
|
65
|
+
sniffer.done
|
66
|
+
|
67
|
+
## if sniffer thread doesn't join immediately kill it off the
|
68
|
+
## capture.each loop blocks if no packets have been seen
|
69
|
+
if sniff_thread.join(0)
|
70
|
+
sniff_thread.kill
|
71
|
+
end
|
data/lib/cmdline.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'pcap'
|
3
|
+
|
4
|
+
class CmdLine
|
5
|
+
def self.parse(args)
|
6
|
+
@config = {}
|
7
|
+
|
8
|
+
opts = OptionParser.new do |opt|
|
9
|
+
opt.on('-i', '--interface=NIC', 'Network interface to sniff (required)') do |nic|
|
10
|
+
@config[:nic] = nic
|
11
|
+
end
|
12
|
+
|
13
|
+
opt.on '-d', '--discard=THRESH', Float, 'Discard keys with request/sec rate below THRESH' do |discard_thresh|
|
14
|
+
@config[:discard_thresh] = discard_thresh
|
15
|
+
end
|
16
|
+
|
17
|
+
opt.on '-r', '--refresh=MS', Float, 'Refresh the stats display every MS milliseconds' do |refresh_rate|
|
18
|
+
@config[:refresh_rate] = refresh_rate
|
19
|
+
end
|
20
|
+
|
21
|
+
opt.on_tail '-h', '--help', 'Show usage info' do
|
22
|
+
puts opts
|
23
|
+
exit
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.parse!
|
28
|
+
|
29
|
+
# bail if we're not root
|
30
|
+
unless Process::Sys.getuid == 0
|
31
|
+
puts "** ERROR: needs to run as root to capture packets"
|
32
|
+
exit 1
|
33
|
+
end
|
34
|
+
|
35
|
+
# we need need a nic to listen on
|
36
|
+
unless @config.has_key?(:nic)
|
37
|
+
puts "** ERROR: You must specify a network interface to listen on"
|
38
|
+
puts opts
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
|
42
|
+
# we can't do 'any' interface just yet due to weirdness with ruby pcap libs
|
43
|
+
if @config[:nic] =~ /any/i
|
44
|
+
puts "** ERROR: can't bind to any interface due to odd issues with ruby-pcap"
|
45
|
+
puts opts
|
46
|
+
exit 1
|
47
|
+
end
|
48
|
+
|
49
|
+
@config
|
50
|
+
end
|
51
|
+
end
|
data/lib/sniffer.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'pcap'
|
2
|
+
|
3
|
+
class MemcacheSniffer
|
4
|
+
attr_accessor :metrics, :semaphore
|
5
|
+
|
6
|
+
def initialize(config)
|
7
|
+
@source = config[:nic]
|
8
|
+
|
9
|
+
@metrics = {}
|
10
|
+
@metrics[:calls] = {}
|
11
|
+
@metrics[:objsize] = {}
|
12
|
+
@metrics[:reqsec] = {}
|
13
|
+
@metrics[:bw] = {}
|
14
|
+
@metrics[:stats] = { :recv => 0, :drop => 0 }
|
15
|
+
|
16
|
+
@semaphore = Mutex.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def start
|
20
|
+
cap = Pcap::Capture.open_live(@source, 1500)
|
21
|
+
|
22
|
+
@metrics[:start_time] = Time.new.to_f
|
23
|
+
|
24
|
+
@done = false
|
25
|
+
|
26
|
+
cap.setfilter('port 11211')
|
27
|
+
cap.loop do |packet|
|
28
|
+
@metrics[:stats] = cap.stats
|
29
|
+
|
30
|
+
# parse key name, and size from VALUE responses
|
31
|
+
if packet.raw_data =~ /VALUE (\S+) \S+ (\S+)/
|
32
|
+
key = $1
|
33
|
+
bytes = $2
|
34
|
+
|
35
|
+
@semaphore.synchronize do
|
36
|
+
if @metrics[:calls].has_key?(key)
|
37
|
+
@metrics[:calls][key] += 1
|
38
|
+
else
|
39
|
+
@metrics[:calls][key] = 1
|
40
|
+
end
|
41
|
+
|
42
|
+
@metrics[:objsize][key] = bytes.to_i
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
break if @done
|
47
|
+
end
|
48
|
+
|
49
|
+
cap.close
|
50
|
+
end
|
51
|
+
|
52
|
+
def done
|
53
|
+
@done = true
|
54
|
+
end
|
55
|
+
end
|
data/lib/ui.rb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'curses'
|
2
|
+
|
3
|
+
include Curses
|
4
|
+
|
5
|
+
class UI
|
6
|
+
def initialize(config)
|
7
|
+
init_screen
|
8
|
+
cbreak
|
9
|
+
curs_set(0)
|
10
|
+
|
11
|
+
# set keyboard input timeout - sneaky way to manage refresh rate
|
12
|
+
Curses.timeout = config.has_key?(:refresh_rate) ? config[:refresh_rate] : 500
|
13
|
+
|
14
|
+
if can_change_color?
|
15
|
+
start_color
|
16
|
+
init_pair(0, COLOR_WHITE, COLOR_BLACK)
|
17
|
+
init_pair(1, COLOR_WHITE, COLOR_BLUE)
|
18
|
+
init_pair(2, COLOR_WHITE, COLOR_RED)
|
19
|
+
end
|
20
|
+
|
21
|
+
@stat_cols = %w[ calls objsize req/sec bw(kbps) ]
|
22
|
+
@stat_col_width = 10
|
23
|
+
@key_col_width = 0
|
24
|
+
|
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
|
+
@commands = {
|
29
|
+
'Q' => "quit",
|
30
|
+
'C' => "sort by calls",
|
31
|
+
'S' => "sort by size",
|
32
|
+
'R' => "sort by req/sec",
|
33
|
+
'B' => "sort by bandwidth",
|
34
|
+
'T' => "toggle sort order (asc|desc)"
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def header
|
39
|
+
# pad stat columns to @stat_col_width
|
40
|
+
@stat_cols = @stat_cols.map { |c| sprintf("%#{@stat_col_width}s", c) }
|
41
|
+
|
42
|
+
# key column width is whatever is left over
|
43
|
+
@key_col_width = cols - (@stat_cols.length * @stat_col_width)
|
44
|
+
|
45
|
+
attrset(color_pair(1))
|
46
|
+
setpos(0,0)
|
47
|
+
addstr(sprintf "%-#{@key_col_width}s%s", "memcache key", @stat_cols.join)
|
48
|
+
end
|
49
|
+
|
50
|
+
def footer
|
51
|
+
footer_text = @commands.map { |k,v| "#{k}:#{v}" }.join(' | ')
|
52
|
+
setpos(lines-1, 0)
|
53
|
+
attrset(color_pair(2))
|
54
|
+
addstr(sprintf "%-#{cols}s", footer_text)
|
55
|
+
end
|
56
|
+
|
57
|
+
def render_stats(sniffer, sort_mode, sort_order = :desc)
|
58
|
+
render_start_t = Time.now.to_f * 1000
|
59
|
+
|
60
|
+
# subtract header + footer lines
|
61
|
+
maxlines = lines - 3
|
62
|
+
offset = 1
|
63
|
+
|
64
|
+
# calculate packet loss ratio
|
65
|
+
if sniffer.metrics[:stats][:recv] > 0
|
66
|
+
loss = (sniffer.metrics[:stats][:drop].to_f / sniffer.metrics[:stats][:recv].to_f) * 100
|
67
|
+
else
|
68
|
+
loss = 0
|
69
|
+
end
|
70
|
+
|
71
|
+
# construct and render footer stats line
|
72
|
+
setpos(lines-2,0)
|
73
|
+
attrset(color_pair(2))
|
74
|
+
header_summary = sprintf "%-28s %-14s %-30s",
|
75
|
+
"sort mode: #{sort_mode.to_s} (#{sort_order.to_s})",
|
76
|
+
"keys: #{sniffer.metrics[:calls].keys.count}",
|
77
|
+
"packets (recv/dropped): #{sniffer.metrics[:stats][:recv]} / #{sniffer.metrics[:stats][:drop]} (#{loss.round(2)}%)"
|
78
|
+
addstr(sprintf "%-#{cols}s", header_summary)
|
79
|
+
|
80
|
+
# reset colours for main key display
|
81
|
+
attrset(color_pair(0))
|
82
|
+
|
83
|
+
top = []
|
84
|
+
|
85
|
+
sniffer.semaphore.synchronize do
|
86
|
+
# we may have seen no packets received on the sniffer thread
|
87
|
+
return if sniffer.metrics[:start_time].nil?
|
88
|
+
|
89
|
+
elapsed = Time.now.to_f - sniffer.metrics[:start_time]
|
90
|
+
|
91
|
+
# iterate over all the keys in the metrics hash and calculate some values
|
92
|
+
sniffer.metrics[:calls].each do |k,v|
|
93
|
+
reqsec = v / elapsed
|
94
|
+
|
95
|
+
# if req/sec is <= the discard threshold delete those keys from
|
96
|
+
# the metrics hash - this is a hack to manage the size of the
|
97
|
+
# metrics hash in high volume environments
|
98
|
+
if reqsec <= @discard_thresh
|
99
|
+
sniffer.metrics[:calls].delete(k)
|
100
|
+
sniffer.metrics[:objsize].delete(k)
|
101
|
+
sniffer.metrics[:reqsec].delete(k)
|
102
|
+
sniffer.metrics[:bw].delete(k)
|
103
|
+
else
|
104
|
+
sniffer.metrics[:reqsec][k] = v / elapsed
|
105
|
+
sniffer.metrics[:bw][k] = ((sniffer.metrics[:objsize][k] * sniffer.metrics[:reqsec][k]) * 8) / 1000
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
top = sniffer.metrics[sort_mode].sort { |a,b| a[1] <=> b[1] }
|
110
|
+
end
|
111
|
+
|
112
|
+
unless sort_order == :asc
|
113
|
+
top.reverse!
|
114
|
+
end
|
115
|
+
|
116
|
+
for i in 0..maxlines-1
|
117
|
+
if i < top.length
|
118
|
+
k = top[i][0]
|
119
|
+
v = top[i][1]
|
120
|
+
|
121
|
+
# if the key is too wide for the column truncate it and add an ellipsis
|
122
|
+
if k.length > @key_col_width
|
123
|
+
display_key = k[0..@key_col_width-4]
|
124
|
+
display_key = "#{display_key}..."
|
125
|
+
else
|
126
|
+
display_key = k
|
127
|
+
end
|
128
|
+
|
129
|
+
# render each key
|
130
|
+
line = sprintf "%-#{@key_col_width}s %9.d %9.d %9.2f %9.2f",
|
131
|
+
display_key,
|
132
|
+
sniffer.metrics[:calls][k],
|
133
|
+
sniffer.metrics[:objsize][k],
|
134
|
+
sniffer.metrics[:reqsec][k],
|
135
|
+
sniffer.metrics[:bw][k]
|
136
|
+
else
|
137
|
+
# we're not clearing the display between renders so erase past
|
138
|
+
# keys with blank lines if there's < maxlines of results
|
139
|
+
line = " "*cols
|
140
|
+
end
|
141
|
+
|
142
|
+
setpos(1+i, 0)
|
143
|
+
addstr(line)
|
144
|
+
end
|
145
|
+
|
146
|
+
# print render time in status bar
|
147
|
+
runtime = (Time.now.to_f * 1000) - render_start_t
|
148
|
+
attrset(color_pair(2))
|
149
|
+
setpos(lines-2, cols-18)
|
150
|
+
addstr(sprintf "rt: %8.3f (ms)", runtime)
|
151
|
+
end
|
152
|
+
|
153
|
+
def input_handler
|
154
|
+
getch
|
155
|
+
end
|
156
|
+
|
157
|
+
def done
|
158
|
+
nocbreak
|
159
|
+
close_screen
|
160
|
+
end
|
161
|
+
end
|
data/mctop.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = "mctop"
|
7
|
+
gem.version = "0.0.3"
|
8
|
+
gem.authors = ["Marcus Barczak"]
|
9
|
+
gem.email = ["marcus@etsy.com"]
|
10
|
+
gem.description = %q{mctop - a realtime memcache key analyzer}
|
11
|
+
gem.summary = %q{mctop - an interactive terminal app for analyzing memcache key activity breaking it out by requests per second, calls, and estimated bandwidth make sure you have the libpcap development libraries installed for the dependencies}
|
12
|
+
gem.homepage = "https://github.com/etsy/mctop/"
|
13
|
+
|
14
|
+
gem.files = `git ls-files`.split($/)
|
15
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
|
18
|
+
gem.add_runtime_dependency 'ruby-pcap', '~> 0.7.8'
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mctop
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Marcus Barczak
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-13 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: ruby-pcap
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.7.8
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.7.8
|
30
|
+
description: mctop - a realtime memcache key analyzer
|
31
|
+
email:
|
32
|
+
- marcus@etsy.com
|
33
|
+
executables:
|
34
|
+
- mctop
|
35
|
+
extensions: []
|
36
|
+
extra_rdoc_files: []
|
37
|
+
files:
|
38
|
+
- .gitignore
|
39
|
+
- Gemfile
|
40
|
+
- LICENSE
|
41
|
+
- README.md
|
42
|
+
- Rakefile
|
43
|
+
- bin/mctop
|
44
|
+
- lib/cmdline.rb
|
45
|
+
- lib/sniffer.rb
|
46
|
+
- lib/ui.rb
|
47
|
+
- mctop.gemspec
|
48
|
+
homepage: https://github.com/etsy/mctop/
|
49
|
+
licenses: []
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ! '>='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
requirements: []
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 1.8.23
|
69
|
+
signing_key:
|
70
|
+
specification_version: 3
|
71
|
+
summary: mctop - an interactive terminal app for analyzing memcache key activity breaking
|
72
|
+
it out by requests per second, calls, and estimated bandwidth make sure you have
|
73
|
+
the libpcap development libraries installed for the dependencies
|
74
|
+
test_files: []
|