diff --git a/README.md b/README.md index da81e36..fd02fdc 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,12 @@ traffic statistics for each key seen. It currently reports on the following met * **req/sec** - the number of requests per second for the key * **bw (kbps)** - the estimated netowrk bandwidth consumed by this key in kilobits-per-second +Optional metrics: + When used with the -c or --detailed-calls flag + * **server calls** - the number of times the key has been requested from the local memcached server since mctop started + * **client calls** - the number of times the key has been requested from this server to an external memcached server since mctop started + + ## Getting it running the quickest way to get it running is to: @@ -42,6 +48,8 @@ the quickest way to get it running is to: -p, --port=PORT Network port to sniff on (default 11211) -d, --discard=THRESH Discard keys with request/sec rate below THRESH -r, --refresh=MS Refresh the stats display every MS milliseconds + -c, --detailed-calls Detailed client/server call stats + -a, --ip-address=1.1.1.1 IP address of memcached instance (used for client/server stats) -h, --help Show usage info ## User interface commands @@ -49,6 +57,8 @@ the quickest way to get it running is to: The following key commands are available in the console UI: * `C` - sort by number of calls +* `E` - sort by number of server calls +* `L` - sort by number of client calls * `S` - sort by object size * `R` - sort by requests/sec * `B` - sort by bandwidth diff --git a/bin/mctop b/bin/mctop index 8973662..fab5107 100755 --- a/bin/mctop +++ b/bin/mctop @@ -46,6 +46,14 @@ until done do done = true when /[Cc]/ sort_mode = :calls + when /[Ee]/ + if @config[:detailed_calls] + sort_mode = :server_calls + end + when /[Ll]/ + if @config[:detailed_calls] + sort_mode = :client_calls + end when /[Ss]/ sort_mode = :objsize when /[Rr]/ diff --git a/lib/cmdline.rb b/lib/cmdline.rb index a7bdd2d..75389c0 100644 --- a/lib/cmdline.rb +++ b/lib/cmdline.rb @@ -1,5 +1,6 @@ require 'optparse' require 'pcap' +require 'socket' class CmdLine def self.parse(args) @@ -16,15 +17,25 @@ def self.parse(args) end @config[:discard_thresh] = 0 - opt.on '-d', '--discard=THRESH', Float, 'Discard keys with request/sec rate below THRESH' do |discard_thresh| + opt.on('-d', '--discard=THRESH', Float, 'Discard keys with request/sec rate below THRESH') do |discard_thresh| @config[:discard_thresh] = discard_thresh end @config[:refresh_rate] = 500 - opt.on '-r', '--refresh=MS', Float, 'Refresh the stats display every MS milliseconds' do |refresh_rate| + opt.on('-r', '--refresh=MS', Float, 'Refresh the stats display every MS milliseconds') do |refresh_rate| @config[:refresh_rate] = refresh_rate end + @config[:detailed_calls] = false + opt.on('-c', '--detailed-calls', 'Detailed client/server call stats') do |detailed_calls| + @config[:detailed_calls] = true + end + + @config[:ip_address] = IPSocket.getaddress(Socket.gethostname) + opt.on('-a', '--ip-address=1.1.1.1', 'IP address of memcached instance (used for client/server stats)') do |ip_address| + @config[:ip_address] = ip_address + end + opt.on_tail '-h', '--help', 'Show usage info' do puts opts exit diff --git a/lib/sniffer.rb b/lib/sniffer.rb index 1c847b2..d8e554c 100644 --- a/lib/sniffer.rb +++ b/lib/sniffer.rb @@ -5,15 +5,20 @@ class MemcacheSniffer attr_accessor :metrics, :semaphore def initialize(config) - @source = config[:nic] - @port = config[:port] + @source = config[:nic] + @port = config[:port] + @detailed_calls = config[:detailed_calls] + @ip = config[:ip_address] + @metrics = {} - @metrics[:calls] = {} - @metrics[:objsize] = {} - @metrics[:reqsec] = {} - @metrics[:bw] = {} - @metrics[:stats] = { :recv => 0, :drop => 0 } + @metrics[:calls] = {} + @metrics[:client_calls] = {} + @metrics[:server_calls] = {} + @metrics[:objsize] = {} + @metrics[:reqsec] = {} + @metrics[:bw] = {} + @metrics[:stats] = { :recv => 0, :drop => 0 } @semaphore = Mutex.new end @@ -40,8 +45,26 @@ def start else @metrics[:calls][key] = 1 end - @metrics[:objsize][key] = bytes.to_i + + if @detailed_calls + # Break down keys by server requests and client requests + if @ip == packet.src.to_s + if @metrics[:server_calls].has_key?(key) + @metrics[:server_calls][key] += 1 + else + @metrics[:server_calls][key] = 1 + end + end + + if @ip == packet.dst.to_s + if @metrics[:client_calls].has_key?(key) + @metrics[:client_calls][key] += 1 + else + @metrics[:client_calls][key] = 1 + end + end + end end end diff --git a/lib/ui.rb b/lib/ui.rb index cf33470..5519b48 100644 --- a/lib/ui.rb +++ b/lib/ui.rb @@ -20,18 +20,33 @@ def initialize(config) init_pair(2, COLOR_WHITE, COLOR_RED) end - @stat_cols = %w[ calls objsize req/sec bw(kbps) ] - @stat_col_width = 10 + if @config[:detailed_calls] + @stat_cols = %w[ calls server client objsize req/sec bw(kbps) ] + else + @stat_cols = %w[ calls objsize req/sec bw(kbps) ] + end + + @stat_col_width = 10 @key_col_width = 0 @commands = { 'Q' => "quit", - 'C' => "sort by calls", + 'C' => "sort by calls" + } + + if @config[:detailed_calls] + @commands.merge!({ + 'E' => "sort by server calls", + 'L' => "sort by client calls" + }) + end + + @commands.merge!({ 'S' => "sort by size", 'R' => "sort by req/sec", 'B' => "sort by bandwidth", 'T' => "toggle sort order (asc|desc)" - } + }) end def header @@ -124,14 +139,31 @@ def render_stats(sniffer, sort_mode, sort_order = :desc) else display_key = k end + + if @config[:detailed_calls] + # Set default values for these if they're not currently set + sniffer.metrics[:server_calls][k] = 0 if sniffer.metrics[:server_calls][k].nil? + sniffer.metrics[:client_calls][k] = 0 if sniffer.metrics[:client_calls][k].nil? - # render each key - line = sprintf "%-#{@key_col_width}s %9.d %9.d %9.2f %9.2f", - display_key, - sniffer.metrics[:calls][k], - sniffer.metrics[:objsize][k], - sniffer.metrics[:reqsec][k], - sniffer.metrics[:bw][k] + # render each key + line = sprintf "%-#{@key_col_width}s %9.d %9.d %9.d %9.d %9.2f %9.2f", + display_key, + sniffer.metrics[:calls][k], + sniffer.metrics[:server_calls][k], + sniffer.metrics[:client_calls][k], + sniffer.metrics[:objsize][k], + sniffer.metrics[:reqsec][k], + sniffer.metrics[:bw][k] + else + # render each key + line = sprintf "%-#{@key_col_width}s %9.d %9.d %9.2f %9.2f", + display_key, + sniffer.metrics[:calls][k], + sniffer.metrics[:objsize][k], + sniffer.metrics[:reqsec][k], + sniffer.metrics[:bw][k] + end + else # we're not clearing the display between renders so erase past # keys with blank lines if there's < maxlines of results