drbdump 1.0

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.
@@ -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
+