mctop-ng 0.0.5

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 004ffca6b0f5ad6e35be31e0b12f1173f7d892af0b06ffa596843cf58cd92eef
4
+ data.tar.gz: af5dadab38f1ac532836c79c62f6c285f2501a3d1a3acad0195fc640a150adaa
5
+ SHA512:
6
+ metadata.gz: d3e8f41d0e12aef9f31405a02839b88487ab18ef2863264fff19d14bf543de5d8e791296d97b4392345071f3bb45975ff9a7ace48b37f2fc314645942af8f7a8
7
+ data.tar.gz: 4846731a8b3c8ce8d4423fc41f1a0fc7e60df1e539130aef3f78bec2a90c74a199bce26824f2e4e844d2d872bdc8803a5797bdad94fa629c80cc31f23f117434
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ vendor
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mctop.gemspec
4
+ gemspec
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,79 @@
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
+ ![](http://etsycodeascraft.files.wordpress.com/2012/12/mctop.jpg)
16
+
17
+ ## How it works
18
+
19
+ mctop sniffs network traffic collecting memcache `VALUE` responses and calculates from
20
+ traffic statistics for each key seen. It currently reports on the following metrics per key:
21
+
22
+ * **calls** - the number of times the key has been called since mctop started
23
+ * **objsize** - the size of the object stored for that key
24
+ * **req/sec** - the number of requests per second for the key
25
+ * **bw (kbps)** - the estimated network bandwidth consumed by this key in kilobits-per-second
26
+
27
+ ## Getting it running
28
+
29
+ the quickest way to get it running is to:
30
+
31
+ * ensure you have libpcap-devel installed
32
+ * git clone this repo
33
+ * in the top level directory of this repo `bundle install` (this will install the deps)
34
+ * then either:
35
+ * install it locally `rake install`; or
36
+ * run it from the repo (good for hacking) `sudo ./bin/mctop --help`
37
+
38
+ ## Command line options
39
+
40
+ Usage: mctop [options]
41
+ -i, --interface=NIC Network interface to sniff (required)
42
+ -p, --port=PORT Network port to sniff on (default 11211)
43
+ --host=HOST Network host to sniff on (default all)
44
+ -d, --discard=THRESH Discard keys with request/sec rate below THRESH
45
+ -r, --refresh=MS Refresh the stats display every MS milliseconds
46
+ -h, --help Show usage info
47
+
48
+ ## User interface commands
49
+
50
+ The following key commands are available in the console UI:
51
+
52
+ * `C` - sort by number of calls
53
+ * `S` - sort by object size
54
+ * `R` - sort by requests/sec
55
+ * `B` - sort by bandwidth
56
+ * `T` - toggle sorting by ascending / descending order
57
+ * `Q` - quits
58
+
59
+ ## Status bar
60
+
61
+ The following details are displayed in the status bar
62
+
63
+ * `sort mode` - the current sort mode and ordering
64
+ * `keys` - total number of keys in the metrics table
65
+ * `packets` - packets received and dropped by libpcap (% is percentage of packets dropped)
66
+ * `rt` - the time taken to sort and render the stats
67
+
68
+ ## Changelog
69
+
70
+ * 2026-03-14 - Add support for Ruby 3.4.x and Meta protocol
71
+ * 2012-12-14 - Now compatible with Ruby 1.8.x (tested on 1.8.7-p371)
72
+
73
+ ## Known issues / Gotchas
74
+
75
+ ### ruby-pcap drops packets at high volume
76
+ 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
77
+
78
+ ### No binary protocol support
79
+ There is currently no support for the binary protocol. However, if someone is using it and would like to submit a patch, it would be welcome.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/mctop ADDED
@@ -0,0 +1,72 @@
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 'rubygems'
10
+ require 'cmdline'
11
+ require 'sniffer'
12
+ require 'ui'
13
+
14
+ @config = CmdLine.parse(ARGV)
15
+
16
+ # instantiate a sniffer and user interface object
17
+ sniffer = MemcacheSniffer.new(@config)
18
+ ui = UI.new(@config)
19
+
20
+ # set default display options
21
+ sort_mode = :reqsec
22
+ sort_order = :desc
23
+ done = false
24
+
25
+ # trap most of the typical signals
26
+ %w[ INT QUIT HUP ].each do |sig|
27
+ Signal.trap(sig) do
28
+ puts "** Caught signal #{sig} - exiting"
29
+ done = true
30
+ end
31
+ end
32
+
33
+ # kick the sniffer thread off
34
+ sniff_thread = Thread.new { sniffer.start }
35
+
36
+ # main loop
37
+ until done do
38
+ ui.header
39
+ ui.footer
40
+ ui.render_stats(sniffer, sort_mode, sort_order)
41
+ refresh
42
+
43
+ key = ui.input_handler
44
+ case key
45
+ when /[Qq]/
46
+ done = true
47
+ when /[Cc]/
48
+ sort_mode = :calls
49
+ when /[Ss]/
50
+ sort_mode = :objsize
51
+ when /[Rr]/
52
+ sort_mode = :reqsec
53
+ when /[Bb]/
54
+ sort_mode = :bw
55
+ when /[Tt]/
56
+ if sort_order == :desc
57
+ sort_order = :asc
58
+ else
59
+ sort_order = :desc
60
+ end
61
+ end
62
+ end
63
+
64
+ ## cleanup
65
+ ui.done
66
+ sniffer.done
67
+
68
+ ## if sniffer thread doesn't join immediately kill it off the
69
+ ## capture.each loop blocks if no packets have been seen
70
+ if sniff_thread.join(0)
71
+ sniff_thread.kill
72
+ end
data/hack/loadgen ADDED
@@ -0,0 +1,23 @@
1
+ #!/bin/bash
2
+
3
+ HOST="127.0.0.1"
4
+ PORT="11211"
5
+ KEYS_COUNT=20
6
+ SLEEP_INTERVAL=0.05
7
+
8
+ while true; do
9
+ # Generate a random key and value
10
+ KEY="mock_key_$((RANDOM % KEYS_COUNT))"
11
+ VALUE="mock_value_$((RANDOM % 100))"
12
+ VALUE_LENGTH=${#VALUE}
13
+
14
+ # Format: set <key> <flags> <exptime> <bytes>\r\n<data>\r\n
15
+ printf "set $KEY 0 60 $VALUE_LENGTH\r\n$VALUE\r\n" | nc -q 0 $HOST $PORT > /dev/null 2>&1
16
+
17
+ for i in {1..5}; do
18
+ printf "get $KEY\r\n" | nc -q 0 $HOST $PORT > /dev/null 2>&1
19
+ done
20
+
21
+ # Small sleep to control the rate
22
+ sleep $SLEEP_INTERVAL
23
+ done
data/lib/cmdline.rb ADDED
@@ -0,0 +1,62 @@
1
+ require 'optparse'
2
+
3
+ class CmdLine
4
+ def self.parse(args)
5
+ @config = {}
6
+
7
+ opts = OptionParser.new do |opt|
8
+ opt.on('-i', '--interface=NIC', 'Network interface to sniff (required)') do |nic|
9
+ @config[:nic] = nic
10
+ end
11
+
12
+ @config[:host] = ''
13
+ opt.on('--host=HOST', 'Network host to sniff on (default all)') do |host|
14
+ @config[:host] = host
15
+ end
16
+
17
+ @config[:port] = 11211
18
+ opt.on('-p', '--port=PORT', 'Network port to sniff on (default 11211)') do |port|
19
+ @config[:port] = port
20
+ end
21
+
22
+ @config[:discard_thresh] = 0
23
+ opt.on '-d', '--discard=THRESH', Float, 'Discard keys with request/sec rate below THRESH' do |discard_thresh|
24
+ @config[:discard_thresh] = discard_thresh
25
+ end
26
+
27
+ @config[:refresh_rate] = 500
28
+ opt.on '-r', '--refresh=MS', Float, 'Refresh the stats display every MS milliseconds' do |refresh_rate|
29
+ @config[:refresh_rate] = refresh_rate
30
+ end
31
+
32
+ opt.on_tail '-h', '--help', 'Show usage info' do
33
+ puts opts
34
+ exit
35
+ end
36
+ end
37
+
38
+ opts.parse!
39
+
40
+ # bail if we're not root
41
+ unless Process::Sys.getuid == 0
42
+ puts "** ERROR: needs to run as root to capture packets"
43
+ exit 1
44
+ end
45
+
46
+ # we need need a nic to listen on
47
+ unless @config.has_key?(:nic)
48
+ puts "** ERROR: You must specify a network interface to listen on"
49
+ puts opts
50
+ exit 1
51
+ end
52
+
53
+ # we can't do 'any' interface just yet due to weirdness with ruby pcap libs
54
+ if @config[:nic] =~ /any/i
55
+ puts "** ERROR: can't bind to any interface due to odd issues with ruby-pcap"
56
+ puts opts
57
+ exit 1
58
+ end
59
+
60
+ @config
61
+ end
62
+ end
data/lib/sniffer.rb ADDED
@@ -0,0 +1,68 @@
1
+ require 'pcaprub'
2
+ require 'thread'
3
+
4
+ class MemcacheSniffer
5
+ attr_accessor :metrics, :semaphore
6
+
7
+ def initialize(config)
8
+ @source = config[:nic]
9
+ @port = config[:port]
10
+ @host = config[:host]
11
+
12
+ @metrics = {}
13
+ @metrics[:calls] = {}
14
+ @metrics[:objsize] = {}
15
+ @metrics[:reqsec] = {}
16
+ @metrics[:bw] = {}
17
+ @metrics[:stats] = { :recv => 0, :drop => 0 }
18
+
19
+ @semaphore = Mutex.new
20
+ end
21
+
22
+ def start
23
+ cap = PCAPRUB::Pcap.open_live(@source, 1500, true, 100)
24
+
25
+ @metrics[:start_time] = Time.new.to_f
26
+
27
+ @done = false
28
+
29
+ if @host == ""
30
+ cap.setfilter("port #{@port}")
31
+ else
32
+ cap.setfilter("host #{@host} and port #{@port}")
33
+ end
34
+
35
+ cap.each do |packet|
36
+ raw_stats = cap.stats
37
+ @metrics[:stats] = {
38
+ :recv => raw_stats["recv"] || 0,
39
+ :drop => raw_stats["drop"] || 0
40
+ }
41
+
42
+ if packet =~ /VALUE (\S+) \S+ (\S+)/
43
+ key = $1
44
+ bytes = $2
45
+ @semaphore.synchronize do
46
+ @metrics[:calls][key] ||= 0
47
+ @metrics[:calls][key] += 1
48
+ @metrics[:objsize][key] = bytes.to_i
49
+ end
50
+ elsif packet =~ /mg (\S+)/
51
+ key = $1
52
+ @semaphore.synchronize do
53
+ @metrics[:calls][key] ||= 0
54
+ @metrics[:calls][key] += 1
55
+ @metrics[:objsize][key] ||= 0
56
+ end
57
+ elsif packet =~ /VA (\d+)/
58
+ bytes = $1
59
+ end
60
+
61
+ break if @done
62
+ end
63
+ end
64
+
65
+ def done
66
+ @done = true
67
+ end
68
+ end
data/lib/ui.rb ADDED
@@ -0,0 +1,173 @@
1
+ require 'curses'
2
+
3
+ include Curses
4
+
5
+ class UI
6
+ def initialize(config)
7
+ @config = config
8
+
9
+ init_screen
10
+ cbreak
11
+ curs_set(0)
12
+
13
+ # set keyboard input timeout - sneaky way to manage refresh rate
14
+ Curses.timeout = @config[:refresh_rate]
15
+
16
+ if can_change_color?
17
+ start_color
18
+ init_pair(0, COLOR_WHITE, COLOR_BLACK)
19
+ init_pair(1, COLOR_WHITE, COLOR_BLUE)
20
+ init_pair(2, COLOR_WHITE, COLOR_RED)
21
+ end
22
+
23
+ @stat_cols = %w[ calls objsize req/sec bw(kbps) ]
24
+ @stat_col_width = 10
25
+ @key_col_width = 0
26
+
27
+ @commands = {
28
+ 'Q' => "quit",
29
+ 'C' => "sort by calls",
30
+ 'S' => "sort by size",
31
+ 'R' => "sort by req/sec",
32
+ 'B' => "sort by bandwidth",
33
+ 'T' => "toggle sort order (asc|desc)"
34
+ }
35
+ end
36
+
37
+ def header
38
+ # pad stat columns to @stat_col_width
39
+ @stat_cols = @stat_cols.map { |c| sprintf("%#{@stat_col_width}s", c) }
40
+
41
+ # key column width is whatever is left over
42
+ @key_col_width = cols - (@stat_cols.length * @stat_col_width)
43
+
44
+ attrset(color_pair(1))
45
+ setpos(0,0)
46
+ addstr(sprintf "%-#{@key_col_width}s%s", "memcache key", @stat_cols.join)
47
+ end
48
+
49
+ def footer
50
+ footer_text = @commands.map { |k,v| "#{k}:#{v}" }.join(' | ')
51
+ setpos(lines-1, 0)
52
+ attrset(color_pair(2))
53
+ addstr(sprintf "%-#{cols}s", footer_text)
54
+ end
55
+
56
+ def render_stats(sniffer, sort_mode, sort_order = :desc)
57
+ render_start_t = Time.now.to_f * 1000
58
+
59
+ # subtract header + footer lines
60
+ maxlines = lines - 3
61
+ offset = 1
62
+
63
+ # calculate packet loss ratio
64
+ if sniffer.metrics[:stats][:recv] > 0
65
+ loss = sprintf("%5.2f", (sniffer.metrics[:stats][:drop].to_f / sniffer.metrics[:stats][:recv].to_f) * 100)
66
+ else
67
+ loss = 0
68
+ end
69
+
70
+ # construct and render footer stats line
71
+ setpos(lines-2,0)
72
+ attrset(color_pair(2))
73
+ header_summary = sprintf "%-28s %-14s %-30s",
74
+ "sort mode: #{sort_mode.to_s} (#{sort_order.to_s})",
75
+ "keys: #{sniffer.metrics[:calls].keys.count}",
76
+ "packets (recv/dropped): #{sniffer.metrics[:stats][:recv]} / #{sniffer.metrics[:stats][:drop]} (#{loss}%)"
77
+ addstr(sprintf "%-#{cols}s", header_summary)
78
+
79
+ # reset colours for main key display
80
+ attrset(color_pair(0))
81
+
82
+ top = []
83
+
84
+ sniffer.semaphore.synchronize do
85
+ # we may have seen no packets received on the sniffer thread
86
+ return if sniffer.metrics[:start_time].nil?
87
+
88
+ elapsed = Time.now.to_f - sniffer.metrics[:start_time]
89
+
90
+ # iterate over all the keys in the metrics hash and calculate some values
91
+ sniffer.metrics[:calls].each do |k,v|
92
+ reqsec = v / elapsed
93
+
94
+ # if req/sec is <= the discard threshold delete those keys from
95
+ # the metrics hash - this is a hack to manage the size of the
96
+ # metrics hash in high volume environments
97
+ if reqsec <= @config[:discard_thresh]
98
+ sniffer.metrics[:calls].delete(k)
99
+ sniffer.metrics[:objsize].delete(k)
100
+ sniffer.metrics[:reqsec].delete(k)
101
+ sniffer.metrics[:bw].delete(k)
102
+ else
103
+ sniffer.metrics[:reqsec][k] = v / elapsed
104
+ sniffer.metrics[:bw][k] = ((sniffer.metrics[:objsize][k] * sniffer.metrics[:reqsec][k]) * 8) / 1000
105
+ end
106
+ end
107
+
108
+ top = sniffer.metrics[sort_mode].sort { |a,b| a[1] <=> b[1] }
109
+ end
110
+
111
+ unless sort_order == :asc
112
+ top.reverse!
113
+ end
114
+
115
+ for i in 0..maxlines-1
116
+ if i < top.length
117
+ k = top[i][0]
118
+ v = top[i][1]
119
+
120
+ # if the key is too wide for the column truncate it and add an ellipsis
121
+ if k.length > @key_col_width
122
+ display_key = k[0..@key_col_width-4]
123
+ display_key = "#{display_key}..."
124
+ else
125
+ display_key = k
126
+ end
127
+
128
+ # render each key
129
+ line = sprintf "%-#{@key_col_width}s %9.d %9.d %9.2f %9.2f",
130
+ display_key,
131
+ sniffer.metrics[:calls][k],
132
+ sniffer.metrics[:objsize][k],
133
+ sniffer.metrics[:reqsec][k],
134
+ sniffer.metrics[:bw][k]
135
+ else
136
+ # we're not clearing the display between renders so erase past
137
+ # keys with blank lines if there's < maxlines of results
138
+ line = " "*cols
139
+ end
140
+
141
+ setpos(1+i, 0)
142
+ addstr(line)
143
+ end
144
+
145
+ # print render time in status bar
146
+ runtime = (Time.now.to_f * 1000) - render_start_t
147
+ attrset(color_pair(2))
148
+ setpos(lines-2, cols-18)
149
+ addstr(sprintf "rt: %8.3f (ms)", runtime)
150
+ end
151
+
152
+ def input_handler
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
167
+ end
168
+
169
+ def done
170
+ nocbreak
171
+ close_screen
172
+ end
173
+ end
data/mctop.gemspec ADDED
@@ -0,0 +1,20 @@
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-ng"
7
+ gem.version = "0.0.5"
8
+ gem.authors = ["Marcus Barczak", "mtmn"]
9
+ gem.email = ["marcus@etsy.com", "miro@haravara.org"]
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/mtmn/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 'pcaprub', '~> 0.13.0'
19
+ gem.add_runtime_dependency 'curses', '~> 1.6'
20
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mctop-ng
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5
5
+ platform: ruby
6
+ authors:
7
+ - Marcus Barczak
8
+ - mtmn
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 1980-01-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pcaprub
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.13.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.13.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: curses
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.6'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.6'
41
+ description: mctop - a realtime memcache key analyzer
42
+ email:
43
+ - marcus@etsy.com
44
+ - miro@haravara.org
45
+ executables:
46
+ - mctop
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - ".gitignore"
51
+ - Gemfile
52
+ - LICENSE
53
+ - README.md
54
+ - Rakefile
55
+ - bin/mctop
56
+ - hack/loadgen
57
+ - lib/cmdline.rb
58
+ - lib/sniffer.rb
59
+ - lib/ui.rb
60
+ - mctop.gemspec
61
+ homepage: https://github.com/mtmn/mctop/
62
+ licenses: []
63
+ metadata: {}
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubygems_version: 3.6.9
79
+ specification_version: 4
80
+ summary: mctop - an interactive terminal app for analyzing memcache key activity breaking
81
+ it out by requests per second, calls, and estimated bandwidth make sure you have
82
+ the libpcap development libraries installed for the dependencies
83
+ test_files: []