solutious-stella 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/CHANGES.txt +36 -0
  2. data/README.textile +162 -0
  3. data/Rakefile +88 -0
  4. data/bin/stella +12 -0
  5. data/bin/stella.bat +12 -0
  6. data/lib/daemonize.rb +56 -0
  7. data/lib/pcaplet.rb +180 -0
  8. data/lib/stella.rb +101 -0
  9. data/lib/stella/adapter/ab.rb +337 -0
  10. data/lib/stella/adapter/base.rb +106 -0
  11. data/lib/stella/adapter/httperf.rb +305 -0
  12. data/lib/stella/adapter/pcap_watcher.rb +221 -0
  13. data/lib/stella/adapter/proxy_watcher.rb +76 -0
  14. data/lib/stella/adapter/siege.rb +341 -0
  15. data/lib/stella/cli.rb +258 -0
  16. data/lib/stella/cli/agents.rb +73 -0
  17. data/lib/stella/cli/base.rb +55 -0
  18. data/lib/stella/cli/language.rb +18 -0
  19. data/lib/stella/cli/localtest.rb +78 -0
  20. data/lib/stella/cli/sysinfo.rb +16 -0
  21. data/lib/stella/cli/watch.rb +278 -0
  22. data/lib/stella/command/base.rb +40 -0
  23. data/lib/stella/command/localtest.rb +358 -0
  24. data/lib/stella/data/domain.rb +82 -0
  25. data/lib/stella/data/http.rb +131 -0
  26. data/lib/stella/logger.rb +84 -0
  27. data/lib/stella/response.rb +85 -0
  28. data/lib/stella/storable.rb +201 -0
  29. data/lib/stella/support.rb +276 -0
  30. data/lib/stella/sysinfo.rb +257 -0
  31. data/lib/stella/test/definition.rb +79 -0
  32. data/lib/stella/test/run/summary.rb +70 -0
  33. data/lib/stella/test/stats.rb +114 -0
  34. data/lib/stella/text.rb +64 -0
  35. data/lib/stella/text/resource.rb +38 -0
  36. data/lib/utils/crypto-key.rb +84 -0
  37. data/lib/utils/domainutil.rb +47 -0
  38. data/lib/utils/escape.rb +302 -0
  39. data/lib/utils/fileutil.rb +78 -0
  40. data/lib/utils/httputil.rb +266 -0
  41. data/lib/utils/mathutil.rb +15 -0
  42. data/lib/utils/stats.rb +88 -0
  43. data/lib/utils/textgraph.rb +267 -0
  44. data/lib/utils/timerutil.rb +58 -0
  45. data/lib/win32/Console.rb +970 -0
  46. data/lib/win32/Console/ANSI.rb +305 -0
  47. data/support/kvm.h +91 -0
  48. data/support/ruby-pcap-takuma-notes.txt +19 -0
  49. data/support/ruby-pcap-takuma-patch.txt +30 -0
  50. data/support/text/en.yaml +80 -0
  51. data/support/text/nl.yaml +7 -0
  52. data/support/useragents.txt +75 -0
  53. data/tests/01-util_test.rb +0 -0
  54. data/tests/02-stella-util_test.rb +42 -0
  55. data/tests/10-stella_test.rb +104 -0
  56. data/tests/11-stella-storable_test.rb +68 -0
  57. data/tests/60-stella-command_test.rb +248 -0
  58. data/tests/80-stella-cli_test.rb +45 -0
  59. data/tests/spec-helper.rb +31 -0
  60. metadata +165 -0
@@ -0,0 +1,305 @@
1
+
2
+
3
+ module Stella
4
+ module Adapter
5
+
6
+ #Usage: httperf [-hdvV] [--add-header S] [--burst-length N] [--client N/N]
7
+ # [--close-with-reset] [--debug N] [--failure-status N]
8
+ # [--help] [--hog] [--http-version S] [--max-connections N]
9
+ # [--max-piped-calls N] [--method S] [--no-host-hdr]
10
+ # [--num-calls N] [--num-conns N] [--period [d|u|e]T1[,T2]]
11
+ # [--port N] [--print-reply [header|body]] [--print-request [header|body]]
12
+ # [--rate X] [--recv-buffer N] [--retry-on-failure] [--send-buffer N]
13
+ # [--server S] [--server-name S] [--session-cookies]
14
+ # [--ssl] [--ssl-ciphers L] [--ssl-no-reuse]
15
+ # [--think-timeout X] [--timeout X] [--uri S] [--verbose] [--version]
16
+ # [--wlog y|n,file] [--wsess N,N,X] [--wsesslog N,X,file]
17
+ # [--wset N,X]
18
+ #
19
+ class Httperf < Stella::Adapter::Base
20
+
21
+
22
+
23
+ attr_accessor :hog, :server, :uri, :num_conns, :num_calls, :rate, :timeout, :think_timeout, :port
24
+ attr_accessor :burst_length, :client, :close_with_reset, :debug, :failure_status
25
+ attr_accessor :help, :http_version, :max_connections, :max_piped_calls, :method, :no_host_hdr
26
+ attr_accessor :period, :print_reply, :print_request, :recv_buffer, :retry_on_failure, :send_buffer
27
+ attr_accessor :server_name, :session_cookies, :ssl, :ssl_ciphers, :ssl_no_reuse, :verbose
28
+
29
+ attr_writer :version, :add_header, :wlog, :wsess, :wsesslog, :wset
30
+
31
+ def initialize(options={}, arguments=[])
32
+
33
+ @name = 'httperf'
34
+ @private_variables = ['private_variables', 'name', 'arguments', 'load_factor', 'working_directory']
35
+ @load_factor = 1
36
+
37
+ super(options, arguments)
38
+ end
39
+
40
+
41
+
42
+ def error
43
+ (File.exists? stderr_path) ? FileUtil.read_file(stderr_path) : "Unknown error"
44
+ end
45
+
46
+ # Before calling run
47
+ def before
48
+
49
+
50
+ end
51
+ def command
52
+ raise CommandNotReady.new(self.class.to_s) unless ready?
53
+
54
+ command = "#{@name} "
55
+
56
+ instance_variables.each do |name|
57
+ canon = name.tr('@', '') # instance_variables returns '@name'
58
+ next if @private_variables.member?(canon)
59
+
60
+ # It's important that we take the value from the getter method
61
+ # because it applies the load factor.
62
+ value = self.send(canon)
63
+ if (value.is_a? Array)
64
+ value.each { |el| command << "--#{canon.tr('_', '-')} #{EscapeUtil.shell_single_word(el.to_s)} " }
65
+ else
66
+ command << "--#{canon.tr('_', '-')} #{EscapeUtil.shell_single_word(value.to_s)} "
67
+ end
68
+
69
+ end
70
+
71
+ command << (@arguments.map { |uri| "'#{uri}'" }).join(' ') unless @arguments.empty?
72
+ command
73
+ end
74
+
75
+ # After calling run
76
+ def after
77
+
78
+
79
+ end
80
+
81
+ def process_arguments(arguments)
82
+
83
+ opts = OptionParser.new
84
+ opts.on('--hog') do @hog = true end
85
+ opts.on('--server=S', String) do |v| @server = v end
86
+ opts.on('--server-name=S', String) do |v| @server_name = v end
87
+ opts.on('--port=N', Integer) do |v| @port = v end
88
+ opts.on('--uri=S', String) do |v| @uri = v end
89
+ opts.on('--num-conns=N', Integer) do |v| @num_conns = v end
90
+ opts.on('--num-calls=N', Integer) do |v| @num_calls = v end
91
+ opts.on('--rate=N', Integer) do |v| @rate = v end
92
+ opts.on('--timeout=N', Integer) do |v| @timeout = v end
93
+ opts.on('--think-timeout=N', Integer) do |v| @think_timeout = v end
94
+
95
+ opts.on('-h', '--help') do |v| @help = true end
96
+ opts.on('-v', '--verbose') do |v| @verbose = true end
97
+ opts.on('-V', '--version') do |v| @version = true end
98
+ opts.on('--close-with-reset') do |v| @close_with_reset = true end
99
+ opts.on('--session-cookies') do |v| @session_cookies = true end
100
+ opts.on('--ssl') do |v| @ssl = true end
101
+ opts.on('--ssl-ciphers') do |v| @ssl_ciphers = true end
102
+ opts.on('--ssl-no-reuse') do |v| @ssl_no_reuse = true end
103
+ opts.on('--no-host-hdr') do |v| @no_host_hdr = true end
104
+ opts.on('--retry-on-failure') do |v| @retry_on_failure = true end
105
+
106
+ opts.on('--add-header=S', String) do |v| @add_header ||= []; @add_header << v; end
107
+ opts.on('--burst-length=N', Integer) do |v| @burst_length = v end
108
+ opts.on('--client=S', String) do |v| @client = v end
109
+ opts.on('-d N', '--debug=N', Integer) do |v| @debug ||= 0; @debug = v end
110
+ opts.on('--failure-status=N', Integer) do |v| @failure_status = v end
111
+
112
+ opts.on('--http-version=S', String) do |v| @http_version = v end
113
+
114
+ opts.on('--max-connections=N', Integer) do |v| @max_connections = v end
115
+ opts.on('--max-piped-calls=N', Integer) do |v| @max_piped_calls = v end
116
+ opts.on('--method=S', String) do |v| @method = v end
117
+
118
+ opts.on('--period=S', String) do |v| @period = v end # TODO: Requires parsing
119
+ opts.on('--print-reply=[S]', String) do |v| @print_reply = v end
120
+ opts.on('--print-request=[S]', String) do |v| @print_request = v end
121
+
122
+ opts.on('--recv-buffer=N', Integer) do |v| @recv_buffer = v end
123
+ opts.on('--send-buffer=N', Integer) do |v| @send_buffer = v end
124
+
125
+
126
+ opts.on('--wlog=S', String) do |v| @wlog = Stella::Util::expand_str(v) end
127
+ opts.on('--wsess=S', String) do |v| @wsess = Stella::Util::expand_str(v, Integer) end
128
+ opts.on('--wsesslog=S', String) do |v| @wsesslog = Stella::Util::expand_str(v) end
129
+ opts.on('--wset=S', String) do |v| @wset = Stella::Util::expand_str(v) end
130
+
131
+ if @wsess
132
+
133
+ end
134
+
135
+ # parse! removes the options it finds.
136
+ # It also fails when it finds unknown switches (i.e. -X)
137
+ # Which should leave only the remaining arguments (URIs in this case)
138
+ opts.parse!(arguments)
139
+
140
+ self.arguments = arguments
141
+
142
+ rescue OptionParser::InvalidOption => ex
143
+ # We want to replace this text so we grab just the name of the argument
144
+ badarg = ex.message.gsub('invalid option: ', '')
145
+ raise InvalidArgument.new(badarg)
146
+ end
147
+
148
+
149
+ def version
150
+ vsn = 0
151
+ Stella::Util.capture_output("#{@name} --version") do |stdout, stderr|
152
+ stdout.join.scan(/httperf\-([\d\.]+)\s/) { |v| vsn = v[0] }
153
+ end
154
+ vsn
155
+ end
156
+
157
+ # loadtest
158
+ #
159
+ # True or false: is the call to siege a load test? If it's a call to help or version or
160
+ # to display the config this with return false. It's no reason for someone to make this
161
+ # call through Stella but it's here for goodness sake.
162
+ def loadtest?
163
+ @uri && !@uri.empty?
164
+ end
165
+ def ready?
166
+ @name && !instance_variables.empty?
167
+ end
168
+
169
+ def add_header(name=false, value=false)
170
+ # This is a hack since we have an instance variable called add_header.
171
+ # I figure this is the best of two evils because I'd rather keep the
172
+ # instance variable naming consistent.
173
+ return @add_header if !name && !value
174
+ @add_header ||= []
175
+ @add_header << "#{name}: #{value}"
176
+ end
177
+
178
+ def user_agent=(list=[])
179
+ return unless list && !list.empty?
180
+ list = list.to_ary
181
+ list.each do |agent|
182
+ add_header("User-Agent", agent)
183
+ end
184
+ end
185
+ def vusers
186
+ @wsess[1]
187
+ end
188
+ def vuser_rate
189
+ "#{vusers}/#{rate}"
190
+ end
191
+
192
+ def vusers=(v)
193
+ 0
194
+ end
195
+ def requests
196
+ @num_conns || (@wsess[0] * @wsess[1])
197
+ end
198
+ def requests=(v)
199
+ 0
200
+ end
201
+ def vuser_requests
202
+ 0
203
+ end
204
+ def wsess
205
+ @wsess.join(',')
206
+ end
207
+
208
+ def wset
209
+ @wset.join(',')
210
+ end
211
+
212
+
213
+ def wsesslog
214
+ @wsesslog.join(',')
215
+ end
216
+ def wlog
217
+ @wlog.join(',')
218
+ end
219
+
220
+ #def concurrent
221
+ # (@concurrent * @load_factor).to_i
222
+ #end
223
+ #def concurrent_f
224
+ # (@concurrent * @load_factor).to_f
225
+ #end
226
+ #def reps
227
+ # @reps
228
+ #end
229
+
230
+
231
+
232
+ # Siege writes the summary to STDERR
233
+ def summary_file
234
+ File.new(stdout_path) if File.exists?(stdout_path)
235
+ end
236
+
237
+ def rc_file
238
+ File.join(@working_directory, "siegerc")
239
+ end
240
+
241
+ def log_file
242
+ File.join(@working_directory, "siege.log")
243
+ end
244
+
245
+ def uris_file
246
+ File.join(@working_directory, File.basename(@file))
247
+ end
248
+
249
+ # httperf --hog --timeout=30 --client=0/1 --server=127.0.0.1 --port=5600 --uri=/ --send-buffer=4096 --recv-buffer=16384 --num-conns=5 --num-calls=1
250
+ # httperf: warning: open file limit > FD_SETSIZE; limiting max. # of open files to FD_SETSIZE
251
+ # Maximum connect burst length: 1
252
+ #
253
+ # Total: connections 5 requests 5 replies 5 test-duration 0.513 s
254
+ #
255
+ # Connection rate: 9.7 conn/s (102.7 ms/conn, <=1 concurrent connections)
256
+ # Connection time [ms]: min 102.1 avg 102.7 max 104.1 median 102.5 stddev 0.8
257
+ # Connection time [ms]: connect 0.2
258
+ # Connection length [replies/conn]: 1.000
259
+ #
260
+ # Request rate: 9.7 req/s (102.7 ms/req)
261
+ # Request size [B]: 62.0
262
+ #
263
+ # Reply rate [replies/s]: min 0.0 avg 0.0 max 0.0 stddev 0.0 (0 samples)
264
+ # Reply time [ms]: response 102.3 transfer 0.1
265
+ # Reply size [B]: header 136.0 content 96.0 footer 0.0 (total 232.0)
266
+ # Reply status: 1xx=0 2xx=5 3xx=0 4xx=0 5xx=0
267
+ #
268
+ # CPU time [s]: user 0.12 system 0.39 (user 22.5% system 75.3% total 97.8%)
269
+ # Net I/O: 2.8 KB/s (0.0*10^6 bps)
270
+ #
271
+ # Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0
272
+ # Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0
273
+
274
+ def summary
275
+ return unless summary_file
276
+
277
+ raw = summary_file.readlines.join
278
+ stats = Stella::Test::Run::Summary.new
279
+
280
+ raw.scan(/Request rate: (\d+?\.\d+?) req.s .(\d+?\.\d+?) ms.req./) do |rate,time|
281
+ stats.transaction_rate = rate.to_f
282
+ stats.response_time = (time.to_f) / 1000
283
+ end
284
+
285
+ raw.scan(/connections (\d+?) requests (\d+?) replies (\d+?) test-duration (\d+\.\d+?) s/) do |conn,req,rep,time|
286
+ stats.elapsed_time = time.to_f
287
+ stats.successful = rep.to_i
288
+ stats.failed = conn.to_i - rep.to_i # maybe this should be from the Error line
289
+ stats.transactions = conn.to_i
290
+ end
291
+
292
+ raw.scan(/header (\d+\.\d+?)\s+.+?\s+.total (\d+\.\d+?)./) do |h,t|
293
+ stats.data_transferred = ((t.to_f || 0 ) / 1_048_576).to_f
294
+ stats.headers_transferred = ((h.to_f || 0 ) / 1_048_576).to_f
295
+ end
296
+ stats.vusers = self.vusers
297
+
298
+ stats
299
+ end
300
+
301
+
302
+
303
+ end
304
+ end
305
+ end
@@ -0,0 +1,221 @@
1
+
2
+ require 'webrick'
3
+ require 'stringio'
4
+ require 'net/dns/packet'
5
+
6
+
7
+ module Stella
8
+ module Adapter
9
+ # Make sure Stella's lib directory is before the system defined ones.
10
+ # We are using a modified version of pcaplet.rb.
11
+ require 'pcap'
12
+ require 'observer'
13
+
14
+ # Stella::Adapter::PcapWatcher
15
+ #
16
+ # Record HTTP or DNS events with Pcap (TCP sniffer). This requires ruby-pcap and the C pcap
17
+ # library as well as root acceess (TCP packet sniffing requires root privileges). If you're
18
+ # running Ruby 1.9, JRuby, or Windows this will not be available on your system.
19
+ # To sniff traffic, you must be on either the machine sending the requests or the machine
20
+ # receiving the requests.
21
+ class PcapWatcher
22
+ include Observable
23
+
24
+ # Building Ruby::Pcap with Ruby 1.9.1
25
+ # RSTRING()->len ia now RSTRING_LEN(), ...
26
+ # see: http://gnufied.org/2007/12/21/mysql-c-bindings-for-ruby-19/#comment-3133
27
+ # see: http://www.rubyinside.com/ruby-1-9-1-preview-released-why-its-a-big-deal-1280.html#comment-37223
28
+ # TRAP_BEG and TRAP_END are also fucked. But the fix is not clear.
29
+ # Basically Ruby::PCap is not ready for 1.9
30
+ # See: http://d.hatena.ne.jp/takuma104/20080210/1202638583
31
+
32
+ # Network interface device ID. eri0, en0, lo0, etc... /sbin/ifconfig -a will tell you.
33
+ attr_accessor :device
34
+ # Buffer size, in bytes, to read from each packet (default: 1500)
35
+ attr_accessor :snaplen
36
+ # Port of the machine sending requests (default: 80)
37
+ attr_accessor :sport
38
+ # Port of the machine receiving requests (default: 80)
39
+ attr_accessor :dport
40
+ # dns or http
41
+ attr_accessor :service
42
+ # udp or tcp
43
+ attr_accessor :protocol
44
+ # Maximum number of packets to sniff
45
+ attr_accessor :maxpacks
46
+
47
+ attr_reader :pcaplet
48
+
49
+ def initialize(options={})
50
+ # The proper service name for dns is "domain"
51
+ @service = options[:service] || 'http'
52
+ @service = 'domain' if options[:service] == 'dns'
53
+
54
+ if @service == 'domain'
55
+ @protocol = 'udp'
56
+ else
57
+ @protocol = options[:protocol] || 'tcp'
58
+ end
59
+
60
+ @dport = options[:port] || Socket::getservbyname(@service)
61
+ @sport = options[:port] || @dport
62
+
63
+ @device = options[:device] || guess_device
64
+ @snaplen = options[:snaplen] || 10000 # 10KB
65
+ @maxpacks = options[:maxpacks] || 100_000
66
+
67
+ Stella::LOGGER.info("Watching interface #{@device} for #{@service} activity on #{@protocol} port #{@dport}")
68
+ end
69
+
70
+ def guess_device
71
+ # NOTE: This should be passed in as a value, not called from the global
72
+ case Stella::SYSINFO.implementation
73
+ when :osx
74
+ "en1" # Pcap.lookupdev returns en0
75
+ else
76
+ Pcap.lookupdev
77
+ end
78
+ end
79
+
80
+ def run
81
+
82
+ if (respond_to? "monitor_#{@service}")
83
+ self.send("monitor_#{@service}")
84
+ else
85
+ raise "Unknown service type (#{@service})"
86
+ end
87
+
88
+ end
89
+
90
+
91
+
92
+ # monitor_domain
93
+ #
94
+ # Use Ruby-Pcap to sniff packets off the network interface.
95
+ #
96
+ # DNS monitor based on: http://www.linuxjournal.com/article/9614
97
+ # Install http://rubyforge.org/projects/net-dns
98
+ #
99
+ # NOTE: Is there a better way to match up a request packet with a
100
+ # response packet?
101
+ # We keep connect a request with the response using the domain name.
102
+ # It's possible that two (or more) requests to be made for the same domain
103
+ # at the same time and the responses could be mixed up. This will affect
104
+ # the exact response time but probably not by much.
105
+ def monitor_domain
106
+
107
+ @pcaplet = Pcaplet.new(:device => @device, :count => @maxpacks)
108
+
109
+ req_filter = Pcap::Filter.new("#{@protocol} and dst port #{@dport}", @pcaplet.capture)
110
+ resp_filter = Pcap::Filter.new("#{@protocol} and src port #{@dport}", @pcaplet.capture)
111
+ @pcaplet.add_filter(req_filter | resp_filter)
112
+ @pcaplet.each_packet do |packet|
113
+ data = packet.udp_data
114
+ case packet
115
+ when req_filter
116
+ dobj = Stella::Data::DomainRequest.new(data)
117
+ dobj.time = packet.time
118
+ dobj.client_ip = packet.ip_src
119
+ dobj.server_ip = packet.ip_dst
120
+
121
+ changed
122
+ notify_observers(:domain_request, dobj)
123
+
124
+ when resp_filter
125
+ dobj = Stella::Data::DomainResponse.new(data)
126
+ dobj.time = packet.time
127
+ dobj.client_ip = packet.ip_dst
128
+ dobj.server_ip = packet.ip_src
129
+
130
+ changed
131
+ notify_observers(:domain_response, dobj)
132
+ end
133
+
134
+ end
135
+
136
+ rescue Interrupt
137
+ after
138
+ exit
139
+ end
140
+
141
+
142
+ def monitor_http
143
+
144
+ @pcaplet = Pcaplet.new(:device => @device, :count => @maxpacks)
145
+
146
+ begin
147
+ req_filter = Pcap::Filter.new("#{@protocol} and dst port #{@dport}", @pcaplet.capture)
148
+ resp_filter = Pcap::Filter.new("#{@protocol} and src port #{@sport}", @pcaplet.capture)
149
+ @pcaplet.add_filter(req_filter | resp_filter)
150
+ @pcaplet.each_packet do |packet|
151
+ data = packet.tcp_data
152
+ next if data.nil?
153
+
154
+
155
+ # NOTE: With HTTP 1.1 keep alive connections, multiple requests can be passed
156
+ # through single connection. This makes it difficult to match responses with
157
+ # requests.
158
+ # NOTE: We don't parse the body of POST and PUT requests because the data can
159
+ # be (and likely is), split across numerous packets. We also only grab 1500
160
+ # bytes from each packet.
161
+ # NOTE: The hostname is taken from the Host header. Requests made without
162
+ # this header (including HTTP 1.0) will contain the local hostname instead.
163
+ # TODO: resolve the hostname from the IP address.
164
+ # There are some helpful methods for doing some of this stuff:
165
+ # http://www.goto.info.waseda.ac.jp/~fukusima/ruby/pcap/doc/TCPPacket.html
166
+ case packet
167
+ when req_filter
168
+ next unless data and data =~ /^(GET|POST|HEAD|DELETE|PUT)\s+(.+?)\s+(HTTP.+?)$/
169
+ dobj = Stella::Data::HTTPRequest.new(data.gsub(/\r?\n/, $/)) # Use the system's line terminators
170
+ dobj.time = packet.time
171
+ dobj.client_ip = packet.ip_src
172
+ dobj.server_ip = packet.ip_dst
173
+
174
+ changed
175
+ notify_observers(:http_request, dobj)
176
+
177
+ when resp_filter
178
+ # NOTE: Some responses do not contain a body in the first packet.
179
+ # TODO: investigate further. Try: http://www.ruby-doc.org/core/classes/Enumerable.html
180
+ next unless data and data =~ /^(HTTP.+)$/
181
+ dobj = Stella::Data::HTTPResponse.new(data.gsub(/\r?\n/, $/))
182
+ dobj.time = packet.time
183
+ dobj.client_ip = packet.ip_dst
184
+ dobj.server_ip = packet.ip_src
185
+
186
+ changed
187
+ notify_observers(:http_response, dobj)
188
+
189
+ end
190
+
191
+ end
192
+ rescue Interrupt
193
+ after
194
+ exit
195
+ rescue => ex
196
+ Stella::LOGGER.error(ex)
197
+ end
198
+
199
+
200
+ end
201
+
202
+ def after
203
+ STDOUT.flush
204
+ stat = @pcaplet.capture.stats
205
+ if stat
206
+ Stella::LOGGER.info("#{$/}#{stat.recv} packets received by filter");
207
+ Stella::LOGGER.info("#{stat.drop} packets dropped by kernel", ''); # with an extra line
208
+ end
209
+ STDOUT.flush
210
+ @pcaplet.capture.close
211
+ delete_observers
212
+ rescue
213
+
214
+ # Ignore errors
215
+ end
216
+
217
+
218
+ end
219
+ end
220
+ end
221
+