nchan_tools 0.1.3 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ebb8bad8e4eab2ac6e0dafd8b93941d8001324158feadbd4638701d45b2ca0e
4
- data.tar.gz: 89351ee1297b3b3bc01535e5da7a8fb2795ce16c0f3da3a7b1a5b89c2d6f4dcf
3
+ metadata.gz: 248e672dca0bd32b3f19b72ebbb6bdf9074cdb71f94ae61d26c5e06019abcba8
4
+ data.tar.gz: 0167f0442b2a325f25b64a93916563b9e53de250dff7401c7471735d7854feec
5
5
  SHA512:
6
- metadata.gz: 5cf14c247f20f07962a1087f6f9adf509576dc6e0b9d6e9285ce30401847c038d5ab072bd259271cac48212ebcdf571283a52e30ee79fabf0e3bd884e801a50c
7
- data.tar.gz: 7dc9a2ad487a014212fa27b936f5bab403f428403eb22bbede56705d17dbfc44daff7b30005b82fe6dfb2289cdb1b9a8857473290e5011584f7e565c556f4e5e
6
+ metadata.gz: 9da652c3162c4911784bf0e2b0442e63aa2a766a213b2edd3dd9ffaef2005a4f66dc7cf5ef3992ca655ebc8782d1c0468b1f14a97b268216b1f746c95b2a6cb9
7
+ data.tar.gz: 4b9b7be540e3a4584cb8eeaaffcedd673e3d657d852639f5946e31e07110b43c1c167df1d39339d87ef4bf091ae560e95a017b7385b6e032da0abc3983d69269
@@ -1,16 +1,15 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'securerandom'
4
- require 'nchan_tools/pubsub'
4
+ require 'nchan_tools/benchmark'
5
5
  require "optparse"
6
6
  require 'timers'
7
7
  require 'json'
8
- require "pry"
9
8
  require "HDRHistogram"
10
9
 
11
10
  verbose = false
12
11
  save_csv = false
13
-
12
+ csv_columns = NchanTools::Benchmark::CSV_COLUMNS_DEFAULT
14
13
  init_args = {}
15
14
 
16
15
  opt_parser=OptionParser.new do |opts|
@@ -20,6 +19,9 @@ opt_parser=OptionParser.new do |opts|
20
19
  opts.on("--csv FILENAME", "Append results to file in CSV format") do |f|
21
20
  save_csv = f
22
21
  end
22
+ opts.on("--csv-columns col1,col2,...", "csv columns list") do |f|
23
+ csv_columns = f.split(/\W+/).map(&:to_sym)
24
+ end
23
25
  opts.on("-t", "--time TIME", "Time to run benchmark") do |v|
24
26
  init_args[:time] = v
25
27
  end
@@ -44,225 +46,13 @@ urls += ARGV
44
46
  begin
45
47
  urls += STDIN.read_nonblock(100000).split /\s*\n+\s*/
46
48
  rescue IO::WaitReadable
49
+ rescue EOFError
47
50
  end
48
51
 
49
52
  urls.uniq!
50
53
 
51
- class Benchan
52
- class BenchmarkError < StandardError
53
- end
54
- def initialize(urls, init_args=nil)
55
- @urls = urls
56
- @n = urls.count
57
- @initializing = 0
58
- @ready = 0
59
- @running = 0
60
- @finished = 0
61
- @subs = []
62
- @results = {}
63
- @failed = {}
64
-
65
- @init_args = init_args
66
-
67
- @hdrh_publish = nil
68
- @hdrh_receive = nil
69
-
70
- subs = []
71
- end
72
-
73
- def run
74
- puts "connecting to #{@n} Nchan server#{@n > 1 ? "s" : ""}..."
75
- @urls.each do |url|
76
- sub = Subscriber.new(url, 1, client: :websocket, timeout: 900000, extra_headers: {"Accept" => "text/x-json-hdrhistogram"})
77
- sub.on_failure do |err|
78
- unless @results[sub]
79
- unless @results[sub.url]
80
- @failed[sub] = true
81
- abort err, sub
82
- end
83
- end
84
- false
85
- end
86
- sub.on_message do |msg|
87
- msg = msg.to_s
88
- case msg
89
- when /^READY/
90
- puts " #{sub.url} ok"
91
- @ready +=1
92
- if @ready == @n
93
- control :run
94
- puts "start benchmark..."
95
- end
96
- when /^RUNNING/
97
- puts " #{sub.url} running"
98
- when /^RESULTS\n/
99
- msg = msg[8..-1]
100
- parsed = JSON.parse msg
101
- @results[sub.url] = parsed
102
- @results[sub.url]["raw"] = msg if @results[sub.url]
103
- 1+1
104
- when /^INITIALIZING/
105
- #do nothing
106
- else
107
- raise BenchmarkError, "unexpected server response: #{msg}"
108
- end
109
- end
110
- @subs << sub
111
- sub.run
112
- sub.wait :ready, 1
113
- if @failed[sub]
114
- puts " #{sub.url} failed"
115
- else
116
- puts " #{sub.url} ok"
117
- end
118
- end
119
- return if @failed.count > 0
120
- puts "initializing benchmark..."
121
- control :init
122
- self.wait
123
- puts "finished."
124
- puts ""
125
- end
126
-
127
- def wait
128
- @subs.each &:wait
129
- end
130
-
131
- def control(msg)
132
- if @init_args && (msg.to_sym ==:init || msg.to_sym ==:initialize)
133
- msg = "#{msg.to_s} #{@init_args.map{|k,v| "#{k}=#{v}"}.join(" ")}"
134
- end
135
- @subs.each { |sub| sub.client.send_data msg.to_s }
136
- end
137
-
138
- def abort(err, src_sub = nil)
139
- puts " #{err}"
140
- @subs.each do |sub|
141
- sub.terminate unless sub == src_sub
142
- end
143
- end
144
-
145
- def hdrhistogram_stats(name, histogram)
146
- fmt = <<-END.gsub(/^ {6}/, '')
147
- %s
148
- min: %.3fms
149
- avg: %.3fms
150
- 99%%ile: %.3fms
151
- max: %.3fms
152
- stddev: %.3fms
153
- samples: %d
154
- END
155
- fmt % [ name,
156
- histogram.min, histogram.mean, histogram.percentile(99.0), histogram.max, histogram.stddev, histogram.count
157
- ]
158
- end
159
-
160
- def results
161
- @channels = 0
162
- @runtime = []
163
- @subscribers = 0
164
- @message_length = []
165
- @messages_sent = 0
166
- @messages_send_confirmed = 0
167
- @messages_send_unconfirmed = 0
168
- @messages_send_failed = 0
169
- @messages_received = 0
170
- @messages_unreceived = 0
171
- @hdrh_publish = nil
172
- @hdrh_receive = nil
173
- @results.each do |url, data|
174
- @channels += data["channels"]
175
- @runtime << data["run_time_sec"]
176
- @subscribers += data["subscribers"]
177
- @message_length << data["message_length"]
178
- @messages_sent += data["messages"]["sent"]
179
- @messages_send_confirmed += data["messages"]["send_confirmed"]
180
- @messages_send_unconfirmed += data["messages"]["send_unconfirmed"]
181
- @messages_send_failed += data["messages"]["send_failed"]
182
- @messages_received += data["messages"]["received"]
183
- @messages_unreceived += data["messages"]["unreceived"]
184
-
185
- if data["message_publishing_histogram"]
186
- hdrh = HDRHistogram.unserialize(data["message_publishing_histogram"], unit: :ms, multiplier: 0.001)
187
- if @hdrh_publish
188
- @hdrh_publish.merge! hdrh
189
- else
190
- @hdrh_publish = hdrh
191
- end
192
- end
193
- if data["message_delivery_histogram"]
194
- hdrh = HDRHistogram.unserialize(data["message_delivery_histogram"], unit: :ms, multiplier: 0.001)
195
- if @hdrh_receive
196
- @hdrh_receive.merge! hdrh
197
- else
198
- @hdrh_receive = hdrh
199
- end
200
- end
201
- end
202
-
203
- @message_length = @message_length.sum.to_f / @message_length.size
204
- @runtime = @runtime.sum.to_f / @runtime.size
205
-
206
- fmt = <<-END.gsub(/^ {6}/, '')
207
- Nchan servers: %d
208
- runtime: %d
209
- channels: %d
210
- subscribers: %d
211
- subscribers per channel: %.1f
212
- messages:
213
- length: %d
214
- sent: %d
215
- send_confirmed: %d
216
- send_unconfirmed: %d
217
- send_failed: %d
218
- received: %d
219
- unreceived: %d
220
- send rate: %.3f/sec
221
- receive rate: %.3f/sec
222
- send rate per channel: %.3f/min
223
- receive rate per subscriber: %.3f/min
224
- END
225
- out = fmt % [
226
- @n, @runtime, @channels, @subscribers, @subscribers.to_f/@channels,
227
- @message_length, @messages_sent, @messages_send_confirmed, @messages_send_unconfirmed, @messages_send_failed,
228
- @messages_received, @messages_unreceived,
229
- @messages_sent.to_f/@runtime,
230
- @messages_received.to_f/@runtime,
231
- (@messages_sent.to_f* 60)/(@runtime * @channels),
232
- (@messages_received.to_f * 60)/(@runtime * @subscribers)
233
- ]
234
-
235
- out << hdrhistogram_stats("message publishing latency", @hdrh_publish) if @hdrh_publish
236
- out << hdrhistogram_stats("message delivery latency", @hdrh_receive) if @hdrh_receive
237
-
238
- puts out
239
- end
240
-
241
- def append_csv_file(file)
242
- require "csv"
243
- write_headers = File.zero?(file)
244
- headers = %i[servers runtime channels subscribers
245
- message_length messages_sent messages_send_confirmed messages_send_unconfirmed messages_send_failed
246
- messages_send_received messages_send_unreceived
247
- messages_send_rate messages_receive_rate messages_send_rate_per_channel messages_receive_rate_per_subscriber
248
- message_publishing_response_avg message_publishing_response_99percentile message_publishing_response_stddev message_publishing_response_count
249
- message_delivery_avg message_delivery_99percentile message_delivery_stddev message_delivery_count]
250
- csv = CSV.open(file, "a", {headers: headers, write_headers: write_headers})
251
- csv << [@n, @runtime, @channels, @subscribers,
252
- @message_length, @messages_sent, @messages_send_confirmed, @messages_send_unconfirmed, @messages_send_failed,
253
- @messages_received, @messages_unreceived,
254
- @messages_sent.to_f/@runtime, @messages_received.to_f/@runtime,
255
- (@messages_sent.to_f* 60)/(@runtime * @channels), (@messages_received.to_f * 60)/(@runtime * @subscribers),
256
- @hdrh_publish.mean, @hdrh_publish.percentile(99.0), @hdrh_publish.max, @hdrh_publish.stddev, @hdrh_publish.count,
257
- @hdrh_receive.mean, @hdrh_receive.percentile(99.0), @hdrh_receive.max, @hdrh_receive.stddev, @hdrh_receive.count
258
- ]
259
- csv.flush
260
- csv.close
261
- end
262
- end
263
-
264
- benchan = Benchan.new urls, init_args
54
+ benchan = NchanTools::Benchmark.new urls, init_args
265
55
  benchan.run
266
56
  benchan.results
267
- benchan.append_csv_file(save_csv) if save_csv
57
+ benchan.append_csv_file(save_csv, csv_columns) if save_csv
268
58
 
@@ -54,7 +54,7 @@ puts "Publishing to #{url}."
54
54
 
55
55
  loopmsg=("\r"*20) + "sending message #"
56
56
 
57
- pub = Publisher.new url, nostore: true, timeout: timeout, verbose: verbose, websocket: websocket
57
+ pub = NchanTools::Publisher.new url, nostore: true, timeout: timeout, verbose: verbose, websocket: websocket
58
58
  pub.accept=accept
59
59
  pub.nofail=true
60
60
  repeat=true
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "redis"
4
+ require "optparse"
5
+ require 'nchan_tools/rdsck'
6
+
7
+ $opt = {
8
+ url: "redis://127.0.0.1:6379/",
9
+ verbose: false,
10
+ command: nil
11
+ }
12
+
13
+ opt_parser=OptionParser.new do |opts|
14
+ opts.on("--url", "--url REDIS_URL (#{$opt[:url]})", "Redis server and port..") do |v|
15
+ $opt[:url]=v
16
+ end
17
+ opts.on("-q", "--quiet", "output only results without any other information") do
18
+ $opt[:quiet]=false
19
+ end
20
+ opts.on("--list-channels", "list all Nchan channels on Redis server or cluster") do |v|
21
+ $opt[:command]=:filter_channels
22
+ end
23
+ opts.on("--filter-channels-min-subscribers=[NUMBER]") do |v|
24
+ $opt[:command]=:filter_channels
25
+ $opt[:min_subscribers]=v.to_i
26
+ end
27
+ end
28
+ opt_parser.banner= <<~EOB
29
+ Debugging tools for the Redis server or cluster backing Nchan.
30
+ Usage: nchan-redis-debug [options]
31
+
32
+ WARNING: --list-channels and --filter-channels-* options for this tool
33
+ use the Redis SCAN command. This may increase CPU load on the Redis
34
+ server and may affect the latency of Nchan requests.
35
+ USE THESE OPTIONS WITH GREAT CARE
36
+
37
+ Example:
38
+ nchan-redis-debug --url redis:// --filter-channels-min-subscribers=10
39
+ EOB
40
+ opt_parser.parse!
41
+
42
+ rdsck = Rdsck.new $opt
43
+ if not rdsck.connect
44
+ STDERR.puts "failed to connect to #{$opt[:url]}"
45
+ exit 1
46
+ end
47
+
48
+ case $opt[:command]
49
+ when :filter_channels
50
+ puts "# scanning for channels #{$opt[:min_subscribers] && "with subscribers >= #{$opt[:min_subscribers]}"}"
51
+ chans = rdsck.filter_channels(min_subscribers: $opt[:min_subscribers])
52
+ puts "# found #{chans.count} channel#{chans.count != 1 && "s"}#{chans.count == 0 ? "." : ":"}"
53
+ puts chans.join("\n")
54
+ else
55
+ puts "Nothing to do"
56
+ end
@@ -33,7 +33,7 @@ opt_parser=OptionParser.new do |opts|
33
33
  opts.on("-p", "--parallel NUM (#{par})", "number of parallel clients"){|v| par = v.to_i}
34
34
  opts.on("-t", "--timeout SEC (#{opt[:timeout]})", "Long-poll timeout"){|v| opt[:timeout] = v}
35
35
  opts.on("-q", "--quit STRING (#{opt[:quit_message]})", "Quit message"){|v| opt[:quit_message] = v}
36
- opts.on("-c", "--client STRING (#{opt[:client]})", "sub client (one of #{Subscriber::Client.unique_aliases.join ', '})") do |v|
36
+ opts.on("-c", "--client STRING (#{opt[:client]})", "sub client (one of #{NchanTools::Subscriber::Client.unique_aliases.join ', '})") do |v|
37
37
  opt[:client] = v.to_sym
38
38
  end
39
39
  opts.on("--content-type", "show received content-type"){|v| print_content_type = true}
@@ -68,7 +68,7 @@ if origin
68
68
  opt[:extra_headers]['Origin'] = origin
69
69
  end
70
70
 
71
- sub = Subscriber.new url, par, opt
71
+ sub = NchanTools::Subscriber.new url, par, opt
72
72
 
73
73
 
74
74
  NOMSGF="\r"*30 + "Received message %i, len:%i"
@@ -111,7 +111,7 @@ sub.on_message do |msg|
111
111
  end
112
112
 
113
113
  sub.on_failure do |err_msg|
114
- if Subscriber::IntervalPollClient === sub.client
114
+ if NchanTools::Subscriber::IntervalPollClient === sub.client
115
115
  unless err_msg.match(/\(code 304\)/)
116
116
  false
117
117
  end
@@ -0,0 +1,241 @@
1
+ require 'nchan_tools/pubsub'
2
+ require 'securerandom'
3
+ require 'timers'
4
+ require 'json'
5
+
6
+ module NchanTools
7
+ class Benchmark
8
+ CSV_COLUMNS_ALL=%i[servers runtime channels channels_K channels_M subscribers message_length messages_sent messages_send_confirmed messages_send_unconfirmed messages_send_failed messages_received messages_unreceived messages_send_rate messages_receive_rate messages_send_rate_per_channel messages_receive_rate_per_subscriber message_publishing_avg message_publishing_99th message_publishing_max message_publishing_stddev message_publishing_count message_delivery_avg message_delivery_99th message_delivery_max message_delivery_stddev message_delivery_count]
9
+ CSV_COLUMNS_DEFAULT=%i[servers runtime channels subscribers message_length messages_sent messages_send_confirmed messages_send_unconfirmed messages_send_failed messages_received messages_unreceived messages_send_rate messages_receive_rate messages_send_rate_per_channel messages_receive_rate_per_subscriber message_publishing_avg message_publishing_99th message_publishing_max message_publishing_stddev message_publishing_count message_delivery_avg message_delivery_99th message_delivery_max message_delivery_stddev message_delivery_count]
10
+ class BenchmarkError < StandardError
11
+ end
12
+ def initialize(urls, init_args=nil)
13
+ @urls = urls
14
+ @n = urls.count
15
+ @initializing = 0
16
+ @ready = 0
17
+ @running = 0
18
+ @finished = 0
19
+ @subs = []
20
+ @results = {}
21
+ @failed = {}
22
+
23
+ @init_args = init_args
24
+ @histograms = {}
25
+
26
+ subs = []
27
+ end
28
+
29
+ def run
30
+ puts "connecting to #{@n} Nchan server#{@n > 1 ? "s" : ""}..."
31
+ @urls.each do |url|
32
+ sub = NchanTools::Subscriber.new(url, 1, client: :websocket, timeout: 900000, extra_headers: {"Accept" => "text/x-json-hdrhistogram"})
33
+ sub.on_failure do |err|
34
+ unless @results[sub]
35
+ unless @results[sub.url]
36
+ @failed[sub] = true
37
+ abort err, sub
38
+ end
39
+ end
40
+ false
41
+ end
42
+ sub.on_message do |msg|
43
+ msg = msg.to_s
44
+ case msg
45
+ when /^READY/
46
+ puts " #{sub.url} ok"
47
+ @ready +=1
48
+ if @ready == @n
49
+ puts "start benchmark..."
50
+ control :run
51
+ end
52
+ when /^RUNNING/
53
+ puts " #{sub.url} running"
54
+ when /^RESULTS\n/
55
+ msg = msg[8..-1]
56
+ parsed = JSON.parse msg
57
+
58
+ #backwards-compatible histogram fields
59
+ parsed["histograms"]||={}
60
+ if parsed[:message_publishing_histogram] then
61
+ parsed[:histograms]["message publishing"]=parsed[:message_publishing_histogram]
62
+ end
63
+ if parsed[:message_delivery_histogram] then
64
+ parsed[:histograms]["message delivery"]=parsed[:message_delivery_histogram]
65
+ end
66
+
67
+ @results[sub.url] = parsed
68
+ @results[sub.url]["raw"] = msg if @results[sub.url]
69
+ sub.client.send_close
70
+ when /^INITIALIZING/
71
+ #do nothing
72
+ else
73
+ raise BenchmarkError, "unexpected server response: #{msg}"
74
+ end
75
+ end
76
+ @subs << sub
77
+ sub.run
78
+ sub.wait :ready, 1
79
+ if @failed[sub]
80
+ puts " #{sub.url} failed"
81
+ else
82
+ puts " #{sub.url} ok"
83
+ end
84
+ end
85
+ return if @failed.count > 0
86
+ puts "initializing benchmark..."
87
+ control :init
88
+ self.wait
89
+ puts "finished."
90
+ puts ""
91
+ end
92
+
93
+ def wait
94
+ @subs.each &:wait
95
+ end
96
+
97
+ def control(msg)
98
+ if @init_args && (msg.to_sym ==:init || msg.to_sym ==:initialize)
99
+ msg = "#{msg.to_s} #{@init_args.map{|k,v| "#{k}=#{v}"}.join(" ")}"
100
+ end
101
+ @subs.each { |sub| sub.client.send_data msg.to_s }
102
+ end
103
+
104
+ def abort(err, src_sub = nil)
105
+ puts " #{err}"
106
+ @subs.each do |sub|
107
+ sub.terminate unless sub == src_sub
108
+ end
109
+ end
110
+
111
+ def hdrhistogram_stats(name, histogram)
112
+ fmt = <<-END.gsub(/^ {6}/, '')
113
+ %s
114
+ min: %.3fms
115
+ avg: %.3fms
116
+ 99%%ile: %.3fms
117
+ max: %.3fms
118
+ stddev: %.3fms
119
+ samples: %d
120
+ END
121
+ fmt % [ name,
122
+ histogram.min, histogram.mean, histogram.percentile(99.0), histogram.max, histogram.stddev, histogram.count
123
+ ]
124
+ end
125
+
126
+ def results
127
+ @channels = 0
128
+ @runtime = []
129
+ @subscribers = 0
130
+ @message_length = []
131
+ @messages_sent = 0
132
+ @messages_send_confirmed = 0
133
+ @messages_send_unconfirmed = 0
134
+ @messages_send_failed = 0
135
+ @messages_received = 0
136
+ @messages_unreceived = 0
137
+ @histograms = {}
138
+ @results.each do |url, data|
139
+ @channels += data["channels"]
140
+ @runtime << data["run_time_sec"]
141
+ @subscribers += data["subscribers"]
142
+ @message_length << data["message_length"]
143
+ @messages_sent += data["messages"]["sent"]
144
+ @messages_send_confirmed += data["messages"]["send_confirmed"]
145
+ @messages_send_unconfirmed += data["messages"]["send_unconfirmed"]
146
+ @messages_send_failed += data["messages"]["send_failed"]
147
+ @messages_received += data["messages"]["received"]
148
+ @messages_unreceived += data["messages"]["unreceived"]
149
+ if data["histograms"]
150
+ data["histograms"].each do |name, str|
151
+ name = name.to_sym
152
+ hdrh = HDRHistogram.unserialize(str, unit: :ms, multiplier: 0.001)
153
+ if @histograms[name]
154
+ @histograms[name].merge! hdrh
155
+ else
156
+ @histograms[name] = hdrh
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ @message_length = @message_length.inject(0, :+).to_f / @message_length.size
163
+ @runtime = @runtime.inject(0, :+).to_f / @runtime.size
164
+
165
+ fmt = <<-END.gsub(/^ {6}/, '')
166
+ Nchan servers: %d
167
+ runtime: %d
168
+ channels: %d
169
+ subscribers: %d
170
+ subscribers per channel: %.1f
171
+ messages:
172
+ length: %d
173
+ sent: %d
174
+ send_confirmed: %d
175
+ send_unconfirmed: %d
176
+ send_failed: %d
177
+ received: %d
178
+ unreceived: %d
179
+ send rate: %.3f/sec
180
+ receive rate: %.3f/sec
181
+ send rate per channel: %.3f/min
182
+ receive rate per subscriber: %.3f/min
183
+ END
184
+ out = fmt % [
185
+ @n, @runtime, @channels, @subscribers, @subscribers.to_f/@channels,
186
+ @message_length, @messages_sent, @messages_send_confirmed, @messages_send_unconfirmed, @messages_send_failed,
187
+ @messages_received, @messages_unreceived,
188
+ @messages_sent.to_f/@runtime,
189
+ @messages_received.to_f/@runtime,
190
+ (@messages_sent.to_f* 60)/(@runtime * @channels),
191
+ (@messages_received.to_f * 60)/(@runtime * @subscribers)
192
+ ]
193
+ @histograms.each do |name, histogram|
194
+ out << hdrhistogram_stats("#{name} latency:", histogram)
195
+ end
196
+
197
+ puts out
198
+ end
199
+
200
+ def append_csv_file(file, columns=Benchmark::CSV_COLUMNS_DEFAULT)
201
+ require "csv"
202
+ write_headers = File.zero?(file) || !File.exists?(file)
203
+ headers = columns
204
+ vals = {
205
+ servers: @n,
206
+ runtime: @runtime,
207
+ channels: @channels,
208
+ channels_K: @channels/1000.0,
209
+ channels_M: @channels/1000000.0,
210
+ subscribers: @subscribers * @channels,
211
+ message_length: @message_length,
212
+ messages_sent: @messages_sent,
213
+ messages_send_confirmed: @messages_send_confirmed,
214
+ messages_send_unconfirmed: @messages_send_unconfirmed,
215
+ messages_send_failed: @messages_send_failed,
216
+ messages_received: @messages_received,
217
+ messages_unreceived: @messages_unreceived,
218
+ messages_send_rate: @messages_sent.to_f/@runtime,
219
+ messages_receive_rate: @messages_received.to_f/@runtime,
220
+ messages_send_rate_per_channel: (@messages_sent.to_f* 60)/(@runtime * @channels),
221
+ messages_receive_rate_per_subscriber: (@messages_received.to_f * 60)/(@runtime * @subscribers * @channels)
222
+ }
223
+ @histograms.each do |name, histogram|
224
+ vals["#{name}_avg".to_sym]=histogram.mean
225
+ vals["#{name}_95th".to_sym]=histogram.percentile(95.0)
226
+ vals["#{name}_99th".to_sym]=histogram.percentile(99.0)
227
+ vals["#{name}_max".to_sym]=histogram.max
228
+ vals["#{name}_stddev".to_sym]=histogram.stddev
229
+ vals["#{name}_count".to_sym]=histogram.count
230
+ end
231
+
232
+ row = []
233
+ headers.each { |header| row << (vals[header.to_sym] || "-")}
234
+
235
+ csv = CSV.open(file, "a", {headers: headers, write_headers: write_headers})
236
+ csv << row
237
+ csv.flush
238
+ csv.close
239
+ end
240
+ end
241
+ end
@@ -3,7 +3,7 @@ require 'typhoeus'
3
3
  require 'json'
4
4
  require 'oga'
5
5
  require 'yaml'
6
- require 'pry'
6
+
7
7
  require 'celluloid/current'
8
8
  require 'date'
9
9
  Typhoeus::Config.memoize = false
@@ -46,15 +46,12 @@ module URI
46
46
  u
47
47
  end
48
48
  end
49
-
50
- $seq = 0
49
+ module NchanTools
51
50
  class Message
52
51
  attr_accessor :content_type, :message, :times_seen, :etag, :last_modified, :eventsource_event
53
52
  def initialize(msg, last_modified=nil, etag=nil)
54
53
  @times_seen=1
55
54
  @message, @last_modified, @etag = msg, last_modified, etag
56
- $seq+=1
57
- @seq = $seq
58
55
  @idhist = []
59
56
  end
60
57
  def serverside_id
@@ -490,6 +487,14 @@ class Subscriber
490
487
  @ws.binary data
491
488
  end
492
489
 
490
+ def send_ping(msg=nil)
491
+ @ws.ping(msg)
492
+ end
493
+
494
+ def send_close(reason=nil, code=1000)
495
+ @ws.close(reason, code)
496
+ end
497
+
493
498
  def write(data)
494
499
  @sock.write data
495
500
  end
@@ -582,11 +587,11 @@ class Subscriber
582
587
  end
583
588
 
584
589
  bundle.ws.on :ping do |ev|
585
- @on_ping.call if @on_ping
590
+ @subscriber.on(:ping).call ev, bundle
586
591
  end
587
592
 
588
593
  bundle.ws.on :pong do |ev|
589
- @on_pong.call if @on_pong
594
+ @subscriber.on(:pong).call ev, bundle
590
595
  end
591
596
 
592
597
  bundle.ws.on :error do |ev|
@@ -648,13 +653,6 @@ class Subscriber
648
653
  end
649
654
  end
650
655
 
651
- def on_ping
652
- @on_ping = Proc.new if block_given?
653
- end
654
- def on_pong
655
- @on_pong = Proc.new if block_given?
656
- end
657
-
658
656
  def listen(bundle)
659
657
  while @ws[bundle]
660
658
  begin
@@ -684,10 +682,10 @@ class Subscriber
684
682
  private :ws_client
685
683
 
686
684
  def send_ping(data=nil)
687
- ws_client.ping data
685
+ ws_client.send_ping data
688
686
  end
689
- def send_close(code=1000, reason=nil)
690
- ws_client.send_close code, reason
687
+ def send_close(reason=nil, code=1000)
688
+ ws_client.send_close reason, code
691
689
  end
692
690
  def send_data(data)
693
691
  ws_client.send_data data
@@ -697,17 +695,18 @@ class Subscriber
697
695
  end
698
696
 
699
697
  def close(bundle)
700
- if bundle
698
+ if bundle then
701
699
  @ws.delete bundle
702
700
  bundle.sock.close unless bundle.sock.closed?
703
701
  end
704
702
  @connected -= 1
705
- if @connected <= 0
706
- binding.pry unless @ws.count == 0
703
+ if @connected <= 0 then
704
+ until @ws.count == 0 do
705
+ sleep 0.1
706
+ end
707
707
  @cooked.signal true
708
708
  end
709
709
  end
710
-
711
710
  end
712
711
 
713
712
  class LongPollClient < Client
@@ -1521,6 +1520,8 @@ class Subscriber
1521
1520
 
1522
1521
  attr_accessor :url, :client, :messages, :max_round_trips, :quit_message, :errors, :concurrency, :waiting, :finished, :client_class, :log
1523
1522
  def initialize(url, concurrency=1, opt={})
1523
+ @empty_block = Proc.new {}
1524
+ @on={}
1524
1525
  @care_about_message_ids=opt[:use_message_id].nil? ? true : opt[:use_message_id]
1525
1526
  @url=url
1526
1527
  @quit_message = opt[:quit_message]
@@ -1615,6 +1616,14 @@ class Subscriber
1615
1616
  @client.poke until_what, timeout
1616
1617
  end
1617
1618
 
1619
+ def on(evt_name = nil, &block)
1620
+ if block_given?
1621
+ @on[evt_name.to_sym] = block
1622
+ else
1623
+ @on[evt_name.to_sym] or @empty_block
1624
+ end
1625
+ end
1626
+
1618
1627
  def on_message(msg=nil, bundle=nil, &block)
1619
1628
  #puts "received message #{msg && msg.to_s[0..15]}"
1620
1629
  if block_given?
@@ -1660,6 +1669,7 @@ class Publisher
1660
1669
  @accept = opt[:accept]
1661
1670
  @verbose = opt[:verbose]
1662
1671
  @on_response = opt[:on_response]
1672
+ @http2 = opt[:http2]
1663
1673
 
1664
1674
  @ws_wait_until_response = true
1665
1675
 
@@ -1800,6 +1810,7 @@ class Publisher
1800
1810
  headers = {:'Content-Type' => content_type, :'Accept' => accept}
1801
1811
  headers[:'X-Eventsource-Event'] = eventsource_event if eventsource_event
1802
1812
  headers.merge! @extra_headers if @extra_headers
1813
+
1803
1814
  post = Typhoeus::Request.new(
1804
1815
  @url,
1805
1816
  headers: headers,
@@ -1807,7 +1818,8 @@ class Publisher
1807
1818
  body: body,
1808
1819
  timeout: @timeout || PUBLISH_TIMEOUT,
1809
1820
  connecttimeout: @timeout || PUBLISH_TIMEOUT,
1810
- verbose: @verbose
1821
+ verbose: @verbose,
1822
+ http_version: @http2 ? :httpv2_0 : :none
1811
1823
  )
1812
1824
  if body && @messages
1813
1825
  msg=Message.new body
@@ -1895,3 +1907,4 @@ class Publisher
1895
1907
 
1896
1908
 
1897
1909
  end
1910
+ end
@@ -0,0 +1,119 @@
1
+ class Rdsck
2
+ attr_accessor :url, :verbose, :namespace
3
+ attr_accessor :redis, :masters
4
+
5
+ def dbg(*args)
6
+ if $opt[:verbose]
7
+ print("# ")
8
+ puts(*args)
9
+ end
10
+ end
11
+
12
+ def initialize(opt)
13
+ @url=opt[:url]
14
+ @verbose=opt[:verbose]
15
+ @namespace=opt[:namespace]
16
+ end
17
+
18
+ def cluster?
19
+ @cluster_mode
20
+ end
21
+
22
+ def connect
23
+ begin
24
+ @redis=Redis.new url: $opt[:url]
25
+ mode = redis.info["redis_mode"]
26
+ rescue StandardError => e
27
+ STDERR.puts e.message
28
+ return false
29
+ end
30
+
31
+ if mode == "cluster"
32
+ @redis.close
33
+ begin
34
+ @redis=Redis.new cluster: [$opt[:url]]
35
+ @redis.ping
36
+ rescue StandardError => e
37
+ STDERR.puts e.message
38
+ return false
39
+ end
40
+
41
+ @cluster_mode = true
42
+ @masters = []
43
+
44
+ redis.connection.each do |c|
45
+ node = Redis.new url: c[:id]
46
+ @masters << node
47
+ end
48
+ else
49
+ @masters = [@redis]
50
+ end
51
+
52
+ dbg "Connected to Redis #{mode == "cluster" ? "cluster" : "server"}"
53
+ (Array === @redis.connection ? @redis.connection : [@redis.connection]) .each do |v|
54
+ dbg " #{v[:id]}"
55
+ end
56
+ self
57
+ end
58
+
59
+ def key(subkey=nil)
60
+ k = "{channel:#{$opt[:namespace]}/#{$opt[:channel_id]}}"
61
+ return subkey ? "#{k}:#{subkey}" : k
62
+ end
63
+
64
+ def info
65
+ channel_hash=@redis.hgetall key
66
+ hash_ttl=@redis.ttl key
67
+ channel_subs=@redis.hgetall key("subscribers")
68
+ #...
69
+ end
70
+
71
+ def filter_channels(filters={})
72
+ script = <<~EOF
73
+ local prev_cursor = ARGV[1]
74
+ local pattern = ARGV[2]
75
+ local scan_batch_size = ARGV[3]
76
+
77
+ local min_subscribers = ARGV[4] and #ARGV[4] > 0 and tonumber(ARGV[4])
78
+
79
+ local cursor, iteration
80
+ if pattern and #pattern > 0 then
81
+ cursor, iteration = unpack(redis.call("SCAN", prev_cursor, "MATCH", pattern, "COUNT", scan_batch_size))
82
+ else
83
+ cursor, iteration = unpack(redis.call("SCAN", prev_cursor, "COUNT", scan_batch_size))
84
+ end
85
+
86
+ local matched = {}
87
+ for _, chankey in pairs(iteration) do
88
+ local match = true
89
+ if min_subscribers then
90
+ match = match and (tonumber(redis.call('HGET', chankey, 'fake_subscribers') or 0) >= min_subscribers)
91
+ end
92
+ if match then
93
+ table.insert(matched, chankey)
94
+ end
95
+ end
96
+
97
+ return {cursor, matched}
98
+ EOF
99
+
100
+ results = []
101
+ batch_size=500
102
+ masters.each do |m|
103
+ hash = m.script "load", script
104
+ cursor, pattern = "0", "{channel:*}"
105
+ loop do
106
+ cursor, batch_results = m.evalsha hash, keys: [], argv: [cursor, pattern, batch_size, filters[:min_subscribers]]
107
+ results += batch_results
108
+ pattern = ""
109
+ break if cursor.to_i == 0
110
+ end
111
+ end
112
+ results
113
+
114
+ results.map! do |key|
115
+ m = key.match(/^\{channel\:(.*)\}$/)
116
+ m[1] || key
117
+ end
118
+ end
119
+ end
@@ -1,3 +1,3 @@
1
1
  module NchanTools
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.8"
3
3
  end
@@ -34,9 +34,9 @@ Gem::Specification.new do |spec|
34
34
  spec.add_dependency 'websocket-extensions'
35
35
  spec.add_dependency "permessage_deflate"
36
36
  spec.add_dependency 'http_parser.rb'
37
- if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.2.2')
38
- spec.add_dependency 'http-2'
39
- end
37
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.2.2')
38
+ spec.add_dependency 'http-2'
39
+ end
40
40
  spec.add_development_dependency "pry"
41
41
  spec.add_development_dependency "bundler", "~> 1.16"
42
42
  spec.add_development_dependency "rake", "~> 10.0"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nchan_tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leo Ponomarev
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-10-06 00:00:00.000000000 Z
11
+ date: 2021-01-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: typhoeus
@@ -212,6 +212,7 @@ email:
212
212
  executables:
213
213
  - nchan-benchmark
214
214
  - nchan-pub
215
+ - nchan-redis-debug
215
216
  - nchan-sub
216
217
  extensions: []
217
218
  extra_rdoc_files: []
@@ -226,16 +227,19 @@ files:
226
227
  - bin/setup
227
228
  - exe/nchan-benchmark
228
229
  - exe/nchan-pub
230
+ - exe/nchan-redis-debug
229
231
  - exe/nchan-sub
230
232
  - lib/nchan_tools.rb
233
+ - lib/nchan_tools/benchmark.rb
231
234
  - lib/nchan_tools/pubsub.rb
235
+ - lib/nchan_tools/rdsck.rb
232
236
  - lib/nchan_tools/version.rb
233
237
  - nchan_tools.gemspec
234
238
  homepage: https://nchan.io
235
239
  licenses:
236
240
  - WTFPL
237
241
  metadata: {}
238
- post_install_message:
242
+ post_install_message:
239
243
  rdoc_options: []
240
244
  require_paths:
241
245
  - lib
@@ -250,9 +254,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
250
254
  - !ruby/object:Gem::Version
251
255
  version: '0'
252
256
  requirements: []
253
- rubyforge_project:
254
- rubygems_version: 2.7.7
255
- signing_key:
257
+ rubygems_version: 3.1.4
258
+ signing_key:
256
259
  specification_version: 4
257
260
  summary: Development and testing utilities for Nchan
258
261
  test_files: []