drbdump 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,318 @@
1
+ ##
2
+ # Collects and displays statistics on captured packets.
3
+
4
+ class DRbDump::Statistics
5
+
6
+ ##
7
+ # Number of DRb exceptions raised
8
+
9
+ attr_accessor :drb_exceptions_raised
10
+
11
+ ##
12
+ # Number of DRb results received
13
+
14
+ attr_accessor :drb_results_received
15
+
16
+ ##
17
+ # Number of DRb messages sent
18
+
19
+ attr_accessor :drb_messages_sent
20
+
21
+ ##
22
+ # Number of DRb packets seen
23
+
24
+ attr_accessor :drb_packet_count
25
+
26
+ ##
27
+ # Records the last timestamp for a message sent between peers
28
+
29
+ attr_accessor :last_peer_send
30
+
31
+ ##
32
+ # Records the last message sent between peers
33
+
34
+ attr_accessor :last_sent_message
35
+
36
+ ##
37
+ # Records statistics about allocations required to send a message. The
38
+ # outer key is the message name while the inner key is the argument count
39
+ # (including block).
40
+
41
+ attr_accessor :message_allocations
42
+
43
+ ##
44
+ # Records statistics about latencies for sent messages. The outer key is
45
+ # the message name while the inner key is the argument count (including
46
+ # block).
47
+ #
48
+ # The recorded latency is from the first packet in the message-send to the
49
+ # last packet in the message result.
50
+
51
+ attr_accessor :message_latencies
52
+
53
+ ##
54
+ # Records statistics about latencies for messages sent between peers
55
+
56
+ attr_accessor :peer_latencies
57
+
58
+ ##
59
+ # Number of Rinda packets seen
60
+
61
+ attr_accessor :rinda_packet_count
62
+
63
+ ##
64
+ # Number of packets seen, including non-DRb traffic
65
+
66
+ attr_accessor :total_packet_count
67
+
68
+ def initialize # :nodoc:
69
+ @drb_exceptions_raised = 0
70
+ @drb_results_received = 0
71
+ @drb_messages_sent = 0
72
+ @drb_packet_count = 0
73
+ @rinda_packet_count = 0
74
+ @total_packet_count = 0
75
+
76
+ # [message][argc]
77
+ @message_allocations = two_level_statistic_hash
78
+ @message_latencies = two_level_statistic_hash
79
+
80
+ # [source][destination]
81
+ @peer_latencies = two_level_statistic_hash
82
+
83
+ @last_peer_send = Hash.new do |sources, source|
84
+ sources[source] = Hash.new
85
+ end
86
+
87
+ @last_sent_message = Hash.new do |sources, source|
88
+ sources[source] = Hash.new
89
+ end
90
+ end
91
+
92
+ ##
93
+ # Adds information from +message+
94
+
95
+ def add_message_send message
96
+ @drb_messages_sent += 1
97
+
98
+ msg = message.message
99
+ argc = message.argument_count
100
+
101
+ source = message.source
102
+ destination = message.destination
103
+
104
+ @last_peer_send[source][destination] = message.timestamp
105
+ @last_sent_message[source][destination] = msg, argc, message.allocations
106
+ end
107
+
108
+ ##
109
+ # Adds information from +result+
110
+
111
+ def add_result result
112
+ source = result.source
113
+ destination = result.destination
114
+
115
+ @drb_results_received += 1
116
+ @drb_exceptions_raised += 1 unless result.status
117
+
118
+ sent_timestamp = @last_peer_send[destination].delete source
119
+ message, argc, allocations = @last_sent_message[destination].delete source
120
+
121
+ return unless sent_timestamp
122
+
123
+ latency = result.timestamp - sent_timestamp
124
+
125
+ @peer_latencies[destination][source].add latency
126
+ @message_latencies[message][argc].add latency
127
+ @message_allocations[message][argc].add allocations + result.allocations
128
+ end
129
+
130
+ ##
131
+ # Adjusts units in +stats+ from +unit+ to milli-unit if the minimum value is
132
+ # below the required threshold
133
+
134
+ def adjust_units stats, unit # :nodoc:
135
+ if stats.first > 0.05 then
136
+ stats << unit
137
+ return stats
138
+ end
139
+
140
+ unit.replace "m#{unit}"
141
+
142
+ stats = stats.map { |stat| stat * 1000 }
143
+
144
+ stats << unit
145
+ end
146
+
147
+ ##
148
+ # Extracts data and column widths from +data+
149
+
150
+ def extract_and_size data # :nodoc:
151
+ max_outer_size = 0
152
+ max_inner_size = 0
153
+ max_count = 0
154
+
155
+ rows = []
156
+
157
+ data.each do |outer_key, inner|
158
+ max_outer_size = [max_outer_size, outer_key.to_s.size].max
159
+
160
+ inner.each do |inner_key, stat|
161
+ count, *rest = stat.to_a
162
+
163
+ rows << [outer_key, inner_key, count, *rest]
164
+
165
+ max_inner_size = [max_inner_size, inner_key.to_s.size].max
166
+ max_count = [max_count, count].max
167
+ end
168
+ end
169
+
170
+ max_count_size = max_count.to_s.size
171
+
172
+ return max_outer_size, max_inner_size, max_count_size, rows
173
+ end
174
+
175
+ ##
176
+ # Merges +allocation_rows+ and +latency_rows+ into a single data set.
177
+
178
+ def merge_results allocation_rows, latency_rows # :nodoc:
179
+ allocations = allocation_rows.group_by { |message, argc,| [message, argc] }
180
+ latencies = latency_rows.group_by { |message, argc,| [message, argc] }
181
+
182
+ allocations.map do |group, (row, _)|
183
+ latency_row, = latencies.delete(group)
184
+
185
+ if latency_row then
186
+ row.concat latency_row.last 4
187
+ else
188
+ row.concat [0, 0, 0, 0]
189
+ end
190
+ end
191
+ end
192
+
193
+ ##
194
+ # Collapses multiple single-message peers in +rows+ to a single row.
195
+
196
+ def multiple_peers count_size, source_size, destination_size, rows # :nodoc:
197
+ rows = rows.sort_by { |_, _, count| -count }
198
+
199
+ rows.map do |source, destination, count, *stats|
200
+ stats = adjust_units stats, 's'
201
+
202
+ '%2$*1$d messages: %4$*3$s to %6$*5$s; ' % [
203
+ count_size, count, source_size, source, destination_size, destination
204
+ ] +
205
+ '%0.3f, %0.3f, %0.3f, %0.3f %s' % stats
206
+ end
207
+ end
208
+
209
+ def per_message_results # :nodoc:
210
+ name_size, argc_size, sends_size, allocation_rows =
211
+ extract_and_size @message_allocations
212
+
213
+ _, _, _, latency_rows = extract_and_size @message_latencies
214
+
215
+ rows = merge_results allocation_rows, latency_rows
216
+
217
+ rows = rows.sort_by { |message, argc, count,| [-count, message, argc] }
218
+
219
+ return name_size, argc_size, sends_size, rows
220
+ end
221
+
222
+ ##
223
+ # Creates a two-level statistic hash with a DRbDump::Statistic as the inner
224
+ # object for the keys.
225
+
226
+ def two_level_statistic_hash # :nodoc:
227
+ Hash.new do |outer, outer_key|
228
+ outer[outer_key] = Hash.new do |inner, inner_key|
229
+ inner[inner_key] = DRbDump::Statistic.new
230
+ end
231
+ end
232
+ end
233
+
234
+ ##
235
+ # Writes all statistics on packets and messages processesed to $stdout
236
+
237
+ def show
238
+ show_basic
239
+ puts
240
+ show_messages
241
+ puts
242
+ show_peers
243
+ end
244
+
245
+ ##
246
+ # Writes basic statistics on packets and messages processed to $stdout
247
+
248
+ def show_basic
249
+ puts "#{@total_packet_count} total packets captured"
250
+ puts "#{@rinda_packet_count} Rinda packets captured"
251
+ puts "#{@drb_packet_count} DRb packets captured"
252
+ puts "#{@drb_messages_sent} messages sent"
253
+ puts "#{@drb_results_received} results received"
254
+ puts "#{@drb_exceptions_raised} exceptions raised"
255
+ end
256
+
257
+ ##
258
+ # Shows peer statistics
259
+
260
+ def show_peers
261
+ source_size, destination_size, count_size, rows =
262
+ extract_and_size @peer_latencies
263
+
264
+ multiple, single = rows.partition { |_, _, count| count > 1 }
265
+
266
+ multiple << single.pop if single.length == 1
267
+
268
+ count_size = [count_size, single.length.to_s.size].max
269
+
270
+ puts 'Peers min, avg, max, stddev:'
271
+ puts multiple_peers count_size, source_size, destination_size, multiple
272
+ puts single_peers count_size, single unless single.empty?
273
+ end
274
+
275
+ ##
276
+ # Shows message-send statistics including arguments per calls, count of
277
+ # calls and average and standard deviation of allocations.
278
+
279
+ def show_messages # :nodoc:
280
+ name_size, argc_size, sends_size, rows = per_message_results
281
+
282
+ output = rows.map do |message, argc, count, *stats|
283
+ allocation_stats = stats.first 4
284
+ latency_stats = adjust_units stats.last(4), 's'
285
+
286
+ '%-2$*1$s (%4$*3$s args) %6$*5$d sent; ' % [
287
+ name_size, message, argc_size, argc, sends_size, count,
288
+ ] +
289
+ '%0.1f, %0.1f, %0.1f, %0.1f allocations; ' % allocation_stats +
290
+ '%0.3f, %0.3f, %0.3f, %0.3f %s' % latency_stats
291
+ end
292
+
293
+ puts 'Messages sent min, avg, max, stddev:'
294
+ puts output
295
+ end
296
+
297
+ ##
298
+ # Displays single peers
299
+
300
+ def single_peers count_size, rows # :nodoc:
301
+ return if rows.empty?
302
+
303
+ statistic = DRbDump::Statistic.new
304
+
305
+ rows.each do |_, _, _, value|
306
+ statistic.add value
307
+ end
308
+
309
+ count, *stats = statistic.to_a
310
+
311
+ stats = adjust_units stats, 's'
312
+
313
+ '%2$*1$d single-message peers ' % [count_size, count] +
314
+ '%0.3f, %0.3f, %0.3f, %0.3f %s' % stats
315
+ end
316
+
317
+ end
318
+
@@ -0,0 +1,91 @@
1
+ require 'minitest/autorun'
2
+ require 'drbdump'
3
+ require 'pp'
4
+ require 'tempfile'
5
+
6
+ # force time zone to mine
7
+ ENV['TZ'] = 'PST8PDT'
8
+
9
+ ##
10
+ # A test case for writing DRbDump tests.
11
+
12
+ class DRbDump::TestCase < MiniTest::Unit::TestCase
13
+
14
+ test = File.expand_path '../../../test', __FILE__
15
+
16
+ ##
17
+ # Dump containing DRb messages with arguments
18
+
19
+ ARG_DUMP = File.join test, 'arg.dump'
20
+
21
+ ##
22
+ # Dump containing a packet with a FIN flag
23
+
24
+ FIN_DUMP = File.join test, 'drb_fin.dump'
25
+
26
+ ##
27
+ # Dump containing HTTP packets
28
+
29
+ HTTP_DUMP = File.join test, 'http.dump'
30
+
31
+ ##
32
+ # Dump containing messages from example/ping.rb
33
+
34
+ PING_DUMP = File.join test, 'ping.dump'
35
+
36
+ ##
37
+ # Dump containing Rinda::RingFinger lookups
38
+
39
+ RING_DUMP = File.join test, 'ring.dump'
40
+
41
+ ##
42
+ # Dump containing a DRb message that is too large
43
+
44
+ TOO_LARGE_DUMP = File.join test, 'too_large_packet.pcap'
45
+
46
+ ##
47
+ # Creates a new drbdump for +file+ and makes it available as @drbdump.
48
+ # Calling this again will create a brand new instance.
49
+
50
+ def drbdump file = PING_DUMP
51
+ @drbdump = DRbDump.new devices: [file]
52
+ @drbdump.instance_variable_set :@running, true
53
+ @drbdump.resolver = resolver
54
+
55
+ @statistics = @drbdump.statistics
56
+
57
+ @drbdump
58
+ end
59
+
60
+ ##
61
+ # Pretty-print minitest diff output
62
+
63
+ def mu_pp obj # :nodoc:
64
+ s = ''
65
+ s = PP.pp obj, s
66
+ s.chomp
67
+ end
68
+
69
+ ##
70
+ # Returns a Capp packet Enumerator for +file+
71
+
72
+ def packets file
73
+ Capp.open(file).loop
74
+ end
75
+
76
+ ##
77
+ # Creates a resolver for addresses in *_DUMP files
78
+
79
+ def resolver
80
+ Tempfile.open 'hosts' do |io|
81
+ io.puts '10.101.28.77 kault'
82
+ io.flush
83
+
84
+ resolver = Resolv::Hosts.new io.path
85
+ resolver.getname '10.101.28.77' # initialize
86
+ resolver
87
+ end
88
+ end
89
+
90
+ end
91
+
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,283 @@
1
+ require 'drbdump/test_case'
2
+
3
+ class TestDRbDump < DRbDump::TestCase
4
+
5
+ def test_class_process_args_count
6
+ options = DRbDump.process_args %w[--count 50]
7
+
8
+ assert_equal 50, options[:count]
9
+
10
+ options = DRbDump.process_args %w[-c 49]
11
+
12
+ assert_equal 49, options[:count]
13
+ end
14
+
15
+ def test_class_process_args_defaults
16
+ options = DRbDump.process_args []
17
+
18
+ assert_equal Float::INFINITY, options[:count]
19
+ assert_equal [], options[:devices]
20
+ assert_equal true, options[:resolve_names]
21
+ assert_equal nil, options[:run_as_directory]
22
+ assert_equal nil, options[:run_as_user]
23
+ end
24
+
25
+ def test_class_process_args_devices
26
+ options = DRbDump.process_args %w[--interface lo0]
27
+
28
+ assert_equal %w[lo0], options[:devices]
29
+
30
+ options = DRbDump.process_args %w[-i lo0]
31
+
32
+ assert_equal %w[lo0], options[:devices]
33
+
34
+ options = DRbDump.process_args %w[-i lo0 -i en0]
35
+
36
+ assert_equal %w[lo0 en0], options[:devices]
37
+ end
38
+
39
+ def test_class_process_args_invalid
40
+ e = nil
41
+ out, err = capture_io do
42
+ e = assert_raises SystemExit do
43
+ DRbDump.process_args %w[--no-such-option]
44
+ end
45
+ end
46
+
47
+ assert_empty out
48
+ assert_match 'Usage', err
49
+ assert_match 'no-such-option', err
50
+
51
+ assert_equal 1, e.status
52
+ end
53
+
54
+ def test_class_process_args_quiet
55
+ options = DRbDump.process_args %w[--quiet]
56
+
57
+ assert options[:quiet]
58
+
59
+ options = DRbDump.process_args %w[-q]
60
+
61
+ assert options[:quiet]
62
+ end
63
+
64
+ def test_class_process_args_resolve_names
65
+ options = DRbDump.process_args %w[-n]
66
+
67
+ refute options[:resolve_names]
68
+ end
69
+
70
+ def test_class_process_args_run_as_directory
71
+ options = DRbDump.process_args %w[--run-as-directory /]
72
+
73
+ assert_equal '/', options[:run_as_directory]
74
+ end
75
+
76
+ def test_class_process_args_run_as_user
77
+ options = DRbDump.process_args %w[--run-as-user nobody]
78
+
79
+ assert_equal 'nobody', options[:run_as_user]
80
+ end
81
+
82
+ def test_close_stream
83
+ drbdump FIN_DUMP
84
+
85
+ packet = packets(FIN_DUMP).first
86
+ @drbdump.drb_streams[packet.source] = true
87
+ @drbdump.incomplete_streams[packet.source] = ''
88
+ @drbdump.incomplete_timestamps[packet.source] = Time.now
89
+
90
+ @drbdump.close_stream packet.source
91
+
92
+ assert_empty @drbdump.drb_streams
93
+ assert_empty @drbdump.incomplete_streams
94
+ assert_empty @drbdump.incomplete_timestamps
95
+ end
96
+
97
+ def test_create_capp
98
+ drbdump RING_DUMP
99
+
100
+ packets = @drbdump.create_capp(RING_DUMP).loop.to_a
101
+
102
+ refute_empty packets
103
+
104
+ packet = packets.first
105
+
106
+ assert packet.udp?
107
+
108
+ assert_equal Rinda::Ring_PORT, packet.udp_header.destination_port
109
+ end
110
+
111
+ def test_display_drb_count
112
+ send_msg = packets(ARG_DUMP).find { |packet| packet.payload =~ /ping/ }
113
+
114
+ drbdump
115
+
116
+ @drbdump.count = 1
117
+
118
+ capture_io do
119
+ @drbdump.display_drb send_msg
120
+ @drbdump.display_drb send_msg
121
+ end
122
+
123
+ assert_equal 1, @statistics.drb_packet_count
124
+ assert_equal 1, @statistics.drb_messages_sent
125
+
126
+ source, destination = @drbdump.resolve_addresses send_msg
127
+
128
+ assert_equal send_msg.timestamp,
129
+ @statistics.last_peer_send[source][destination]
130
+ end
131
+
132
+ def test_display_drb_http
133
+ drbdump
134
+
135
+ assert_silent do
136
+ packets(HTTP_DUMP).each do |packet|
137
+ @drbdump.display_drb packet
138
+ end
139
+ end
140
+
141
+ assert_equal 0, @statistics.drb_packet_count
142
+
143
+ expected = {
144
+ '17.149.160.49.80' => false,
145
+ '10.101.28.77.53600' => false,
146
+ }
147
+
148
+ assert_equal expected, @drbdump.drb_streams
149
+ end
150
+
151
+ def test_display_drb_incomplete
152
+ drbdump
153
+
154
+ out, = capture_io do
155
+ packets(FIN_DUMP).each do |packet|
156
+ @drbdump.display_drb packet
157
+ end
158
+ end
159
+
160
+ expected = <<-EXPECTED
161
+ 22:19:38.279650 "druby://kault:56128" \u21d2 ("druby://kault:56126", nil).ping(1)
162
+ 22:19:38.280108 "druby://kault:56128" \u21d0 "druby://kault:56126" success: 1
163
+ 22:19:38.280472 "druby://kault:56128" \u2902 "druby://kault:56126" exception: #<DRb::DRbConnError: connection closed>
164
+ 22:19:38.280713 "druby://kault:56129" \u21d2 ("druby://kault:56126", nil).ping(2)
165
+ 22:19:38.280973 "druby://kault:56129" \u21d0 "druby://kault:56126" success: 2
166
+ 22:19:38.281197 "druby://kault:56129" \u2902 "druby://kault:56126" exception: #<DRb::DRbConnError: connection closed>
167
+ EXPECTED
168
+
169
+ assert_equal expected, out
170
+
171
+ assert_empty @drbdump.incomplete_streams
172
+ assert_empty @drbdump.incomplete_timestamps
173
+
174
+ assert_equal 4, @statistics.drb_results_received
175
+ assert_equal 2, @statistics.drb_messages_sent
176
+ end
177
+
178
+ def test_display_drb_too_large
179
+ out, = capture_io do
180
+ packets(TOO_LARGE_DUMP).each do |packet|
181
+ drbdump.display_drb packet
182
+ end
183
+ end
184
+
185
+ innards = "\x04\bI\"\x04\x00\x00\xE0\x01"
186
+ innards << ' ' * 468
187
+
188
+ expected = <<-EXPECTED
189
+ 22:41:07.060619 "druby://kault:56430" to "druby://kault:56428" packet too large, valid: [nil, "<<", 1] too big (31457294 bytes): #{innards.dump}
190
+ EXPECTED
191
+
192
+ assert_equal expected, out
193
+
194
+ assert_equal 0, @statistics.drb_packet_count
195
+ end
196
+
197
+ def test_display_drb_too_large_quiet
198
+ drbdump
199
+ @drbdump.quiet = true
200
+
201
+ assert_silent do
202
+ packets(TOO_LARGE_DUMP).each do |packet|
203
+ @drbdump.display_drb packet
204
+ end
205
+ end
206
+ end
207
+
208
+ def test_display_ring_finger
209
+ out, = capture_io do
210
+ drbdump.display_ring_finger packets(RING_DUMP).first
211
+ end
212
+
213
+ expected = <<-EXPECTED
214
+ 19:39:25.877246 find ring on 255.255.255.255.7647 for druby://kault.jijo.segment7.net:53578 timeout: 5
215
+ EXPECTED
216
+
217
+ assert_equal expected, out
218
+
219
+ assert_equal 1, @statistics.rinda_packet_count
220
+ end
221
+
222
+ def test_display_ring_finger_quiet
223
+ drbdump
224
+ @drbdump.quiet = true
225
+
226
+ assert_silent do
227
+ @drbdump.display_ring_finger packets(RING_DUMP).first
228
+ end
229
+
230
+ assert_equal 1, @statistics.rinda_packet_count
231
+ end
232
+
233
+ def test_load_marshal_data
234
+ drbdump
235
+
236
+ marshal_structure = Marshal::Structure.new "\x04\x08i\x06"
237
+
238
+ assert_equal 1, @drbdump.load_marshal_data(marshal_structure)
239
+
240
+ marshal_structure = Marshal::Structure.new "\x04\x08o:\x06C\x00"
241
+
242
+ loaded = @drbdump.load_marshal_data marshal_structure
243
+
244
+ assert_kind_of DRb::DRbUnknown, loaded
245
+ end
246
+
247
+ def test_start_capture
248
+ drbdump RING_DUMP
249
+
250
+ capp = @drbdump.create_capp RING_DUMP
251
+
252
+ thread, = @drbdump.start_capture [capp]
253
+
254
+ thread.join
255
+
256
+ refute_empty @drbdump.incoming_packets
257
+
258
+ packet = @drbdump.incoming_packets.deq
259
+
260
+ assert packet.udp?
261
+
262
+ assert_equal Rinda::Ring_PORT, packet.udp_header.destination_port
263
+
264
+ assert_equal packets(RING_DUMP).count, @statistics.total_packet_count
265
+ end
266
+
267
+ def test_start_capture_rst_fin
268
+ drbdump FIN_DUMP
269
+
270
+ packet = packets(FIN_DUMP).first
271
+ @drbdump.drb_streams[packet.source] = true
272
+
273
+ capp = @drbdump.create_capp FIN_DUMP
274
+
275
+ thread, = @drbdump.start_capture [capp]
276
+
277
+ thread.join
278
+
279
+ assert_empty @drbdump.drb_streams
280
+ end
281
+
282
+ end
283
+