From 3ff5ae5654919e3f79600c7cdca5709b328721fa Mon Sep 17 00:00:00 2001 From: Jason Camp Date: Sun, 16 Dec 2012 15:15:52 -0500 Subject: [PATCH 1/6] Added columns for client vs server key accesses --- lib/sniffer.rb | 40 +++++++++++++++++++++++++++++++--------- lib/ui.rb | 16 +++++++++++++--- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/lib/sniffer.rb b/lib/sniffer.rb index 1c847b2..69b578e 100644 --- a/lib/sniffer.rb +++ b/lib/sniffer.rb @@ -1,5 +1,6 @@ require 'pcap' require 'thread' +require 'socket' class MemcacheSniffer attr_accessor :metrics, :semaphore @@ -8,12 +9,17 @@ def initialize(config) @source = config[:nic] @port = config[:port] + # uses default interface, figure out how to get the specific interface's ip + @ip = IPSocket.getaddress(Socket.gethostname) + @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 @@ -35,13 +41,29 @@ def start bytes = $2 @semaphore.synchronize do - if @metrics[:calls].has_key?(key) - @metrics[:calls][key] += 1 + if @metrics[:calls].has_key?(key) + @metrics[:calls][key] += 1 + else + @metrics[:calls][key] = 1 + end + @metrics[:objsize][key] = bytes.to_i + + # 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[:calls][key] = 1 + @metrics[:server_calls][key] = 1 end + elsif @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 + - @metrics[:objsize][key] = bytes.to_i end end diff --git a/lib/ui.rb b/lib/ui.rb index cf33470..4bc7e90 100644 --- a/lib/ui.rb +++ b/lib/ui.rb @@ -20,8 +20,8 @@ def initialize(config) init_pair(2, COLOR_WHITE, COLOR_RED) end - @stat_cols = %w[ calls objsize req/sec bw(kbps) ] - @stat_col_width = 10 + @stat_cols = %w[ calls server client objsize req/sec bw(kbps) ] + @stat_col_width = 10 @key_col_width = 0 @commands = { @@ -124,11 +124,21 @@ def render_stats(sniffer, sort_mode, sort_order = :desc) else display_key = k end + + if sniffer.metrics[:server_calls][k].nil? + sniffer.metrics[:server_calls][k] = 0 + end + + if sniffer.metrics[:client_calls][k].nil? + sniffer.metrics[:client_calls][k] = 0 + end # render each key - line = sprintf "%-#{@key_col_width}s %9.d %9.d %9.2f %9.2f", + 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] From dca2810631ef0e92b8995d7bf1c12a65e5833ac0 Mon Sep 17 00:00:00 2001 From: Jason Camp Date: Sun, 16 Dec 2012 15:33:54 -0500 Subject: [PATCH 2/6] Added sorting for server and client calls --- bin/mctop | 4 ++++ lib/ui.rb | 2 ++ 2 files changed, 6 insertions(+) diff --git a/bin/mctop b/bin/mctop index 8973662..c5d98a8 100755 --- a/bin/mctop +++ b/bin/mctop @@ -46,6 +46,10 @@ until done do done = true when /[Cc]/ sort_mode = :calls + when /[Ee]/ + sort_mode = :server_calls + when /[Ll]/ + sort_mode = :client_calls when /[Ss]/ sort_mode = :objsize when /[Rr]/ diff --git a/lib/ui.rb b/lib/ui.rb index 4bc7e90..0adaa54 100644 --- a/lib/ui.rb +++ b/lib/ui.rb @@ -27,6 +27,8 @@ def initialize(config) @commands = { 'Q' => "quit", 'C' => "sort by calls", + 'E' => "sort by server calls", + 'L' => "sort by client calls", 'S' => "sort by size", 'R' => "sort by req/sec", 'B' => "sort by bandwidth", From 2302d29635498f2c49031a141c07f031f94fab38 Mon Sep 17 00:00:00 2001 From: Jason Camp Date: Sun, 16 Dec 2012 15:47:41 -0500 Subject: [PATCH 3/6] Cleaned up code that sets default server/client values --- lib/ui.rb | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/ui.rb b/lib/ui.rb index 0adaa54..ade76c6 100644 --- a/lib/ui.rb +++ b/lib/ui.rb @@ -127,13 +127,9 @@ def render_stats(sniffer, sort_mode, sort_order = :desc) display_key = k end - if sniffer.metrics[:server_calls][k].nil? - sniffer.metrics[:server_calls][k] = 0 - end - - if sniffer.metrics[:client_calls][k].nil? - sniffer.metrics[:client_calls][k] = 0 - end + # 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.d %9.d %9.2f %9.2f", From 23e7a2571f960792f301101c6d9d97a1e749d191 Mon Sep 17 00:00:00 2001 From: Jason Camp Date: Sun, 16 Dec 2012 20:52:37 -0500 Subject: [PATCH 4/6] Made client/server stats optional, updated documentation, changed metrics collection to check for client AND server, not just client or server --- bin/mctop | 8 +++++-- lib/cmdline.rb | 5 +++++ lib/sniffer.rb | 43 ++++++++++++++++++++----------------- lib/ui.rb | 58 +++++++++++++++++++++++++++++++++++--------------- 4 files changed, 75 insertions(+), 39 deletions(-) diff --git a/bin/mctop b/bin/mctop index c5d98a8..fab5107 100755 --- a/bin/mctop +++ b/bin/mctop @@ -47,9 +47,13 @@ until done do when /[Cc]/ sort_mode = :calls when /[Ee]/ - sort_mode = :server_calls + if @config[:detailed_calls] + sort_mode = :server_calls + end when /[Ll]/ - sort_mode = :client_calls + 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..46bbd19 100644 --- a/lib/cmdline.rb +++ b/lib/cmdline.rb @@ -25,6 +25,11 @@ def self.parse(args) @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 + opt.on_tail '-h', '--help', 'Show usage info' do puts opts exit diff --git a/lib/sniffer.rb b/lib/sniffer.rb index 69b578e..8ad1a10 100644 --- a/lib/sniffer.rb +++ b/lib/sniffer.rb @@ -8,6 +8,7 @@ class MemcacheSniffer def initialize(config) @source = config[:nic] @port = config[:port] + @detailed_calls = config[:detailed_calls] # uses default interface, figure out how to get the specific interface's ip @ip = IPSocket.getaddress(Socket.gethostname) @@ -41,29 +42,31 @@ def start bytes = $2 @semaphore.synchronize do - if @metrics[:calls].has_key?(key) - @metrics[:calls][key] += 1 - else - @metrics[:calls][key] = 1 - end - @metrics[:objsize][key] = bytes.to_i - - # 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 - elsif @ip == packet.dst.to_s - if @metrics[:client_calls].has_key?(key) - @metrics[:client_calls][key] += 1 + if @metrics[:calls].has_key?(key) + @metrics[:calls][key] += 1 else - @metrics[:client_calls][key] = 1 + @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 ade76c6..5519b48 100644 --- a/lib/ui.rb +++ b/lib/ui.rb @@ -20,20 +20,33 @@ def initialize(config) init_pair(2, COLOR_WHITE, COLOR_RED) end - @stat_cols = %w[ calls server client objsize req/sec bw(kbps) ] + 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", - 'E' => "sort by server calls", - 'L' => "sort by client 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 @@ -127,19 +140,30 @@ def render_stats(sniffer, sort_mode, sort_order = :desc) display_key = k end - # 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? + 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.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] + # 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 From 3dc76060073f65377b63e5ffc4cf90f843551bd7 Mon Sep 17 00:00:00 2001 From: Jason Camp Date: Sun, 16 Dec 2012 20:54:41 -0500 Subject: [PATCH 5/6] Updated documentation --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index da81e36..9622844 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,7 @@ 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 -h, --help Show usage info ## User interface commands @@ -49,6 +56,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 From d4e005e854f7bab1d4ff9811220e6c55f64762a4 Mon Sep 17 00:00:00 2001 From: Jason Camp Date: Sun, 16 Dec 2012 21:46:19 -0500 Subject: [PATCH 6/6] Added ip address acquisition and option to override --- README.md | 1 + lib/cmdline.rb | 12 +++++++++--- lib/sniffer.rb | 8 +++----- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 9622844..fd02fdc 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ the quickest way to get it running is to: -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 diff --git a/lib/cmdline.rb b/lib/cmdline.rb index 46bbd19..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,20 +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| + 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 8ad1a10..d8e554c 100644 --- a/lib/sniffer.rb +++ b/lib/sniffer.rb @@ -1,17 +1,15 @@ require 'pcap' require 'thread' -require 'socket' 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] - # uses default interface, figure out how to get the specific interface's ip - @ip = IPSocket.getaddress(Socket.gethostname) @metrics = {} @metrics[:calls] = {}