nchan_tools 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 03c7684cee5fb57143e8411351aea95d4bcb989cf8f2d66ebe716655f8f07b09
4
+ data.tar.gz: ed22f3cdd7eb55acab2ad29684daacd4e09edf0a3ffda1c4e2d5309affa896f3
5
+ SHA512:
6
+ metadata.gz: 3009d9a05e5f8d6d81302cf81389be78ef165621fd433fd08beb1d16d0dcba5fa7cbcc6dd56a83a86a6d4756dcd609fa33136031bb4a50077f0d811405aef0ce
7
+ data.tar.gz: cae127fd3166e2442625f3a177e6060561fb8fb0056dc63cc14f03b89404c80a87e1cfd9ee2ed3a0078f91e6665c4b090492f257e5e308368af1cff32a0e23c1
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
@@ -0,0 +1,7 @@
1
+ In the spirit of inclusiveness, cooperation and community, this software is
2
+ distributed with the strictly enforced CYHTFYW Code of Conduct. Failure to
3
+ comply with this code may result in punitive action and loss of privilege.
4
+
5
+ ## CYHTFYW Code of Conduct:
6
+
7
+ 1. Conduct yourself however the fuck you want.
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in nchan_tools.gemspec
6
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Leo Ponomarev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # NchanTools
2
+
3
+ these here be development tools for Nchan. Documentation forthcoming someday maybe.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "nchan_tools"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,214 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'securerandom'
4
+ require 'nchan_tools/pubsub'
5
+ require "optparse"
6
+ require 'timers'
7
+ require 'json'
8
+ require "pry"
9
+ require "HDRHistogram"
10
+
11
+ verbose = false
12
+
13
+ opt_parser=OptionParser.new do |opts|
14
+ opts.on("-v", "--verbose", "somewhat rather extraneously wordful output") do
15
+ verbose = true
16
+ end
17
+ end
18
+ opt_parser.banner="Usage: benchan.rb [options] url1 url2 url3..."
19
+ opt_parser.parse!
20
+
21
+ urls = []
22
+ urls += ARGV
23
+ begin
24
+ urls += STDIN.read_nonblock(100000).split /\s*\n+\s*/
25
+ rescue IO::WaitReadable
26
+ end
27
+
28
+ urls.uniq!
29
+
30
+ class Benchan
31
+ def initialize(urls)
32
+ @urls = urls
33
+ @n = urls.count
34
+ @initializing = 0
35
+ @ready = 0
36
+ @running = 0
37
+ @finished = 0
38
+ @subs = []
39
+ @results = {}
40
+ @failed = {}
41
+
42
+ @hdrh_publish = nil
43
+ @hdrh_receive = nil
44
+
45
+ subs = []
46
+ end
47
+
48
+ def run
49
+ puts "connecting to #{@n} Nchan server#{@n > 1 ? "s" : ""}..."
50
+ @urls.each do |url|
51
+ sub = Subscriber.new(url, 1, client: :websocket, timeout: 900000, extra_headers: {"Accept" => "text/x-json-hdrhistogram"})
52
+ sub.on_failure do |err|
53
+ unless @results[sub]
54
+ unless @results[sub.url]
55
+ @failed[sub] = true
56
+ abort err, sub
57
+ end
58
+ end
59
+ false
60
+ end
61
+ sub.on_message do |msg|
62
+ msg = msg.to_s
63
+ case msg
64
+ when "READY"
65
+ puts " #{sub.url} ok"
66
+ @ready +=1
67
+ if @ready == @n
68
+ control :run
69
+ puts "start benchmark..."
70
+ end
71
+ when "RUNNING"
72
+ puts " #{sub.url} running"
73
+ when /^RESULTS\n/
74
+ msg = msg[8..-1]
75
+ parsed = JSON.parse msg
76
+ @results[sub.url] = parsed
77
+ 1+1
78
+ else
79
+ binding.pry
80
+ 1+1
81
+ end
82
+ end
83
+ @subs << sub
84
+ sub.run
85
+ sub.wait :ready, 1
86
+ if @failed[sub]
87
+ puts " #{sub.url} failed"
88
+ else
89
+ puts " #{sub.url} ok"
90
+ end
91
+ end
92
+ return if @failed.count > 0
93
+ puts "initializing benchmark..."
94
+ control :initialize
95
+ self.wait
96
+ puts "finished."
97
+ puts ""
98
+ end
99
+
100
+ def wait
101
+ @subs.each &:wait
102
+ end
103
+
104
+ def control(msg)
105
+ @subs.each { |sub| sub.client.send_data msg.to_s }
106
+ end
107
+
108
+ def abort(err, src_sub = nil)
109
+ puts " #{err}"
110
+ @subs.each do |sub|
111
+ sub.terminate unless sub == src_sub
112
+ end
113
+ end
114
+
115
+ def hdrhistogram_stats(name, histogram)
116
+ fmt = <<-END.gsub(/^ {6}/, '')
117
+ %s
118
+ min: %.3fms
119
+ avg: %.3fms
120
+ 99%%ile: %.3fms
121
+ max: %.3fms
122
+ stddev: %.3fms
123
+ samples: %d
124
+ END
125
+ fmt % [ name,
126
+ histogram.min, histogram.mean, histogram.percentile(99.0), histogram.max, histogram.stddev, histogram.count
127
+ ]
128
+ end
129
+
130
+ def results
131
+ channels = 0
132
+ runtime = []
133
+ subscribers = 0
134
+ message_length = []
135
+ messages_sent = 0
136
+ messages_send_confirmed = 0
137
+ messages_send_unconfirmed = 0
138
+ messages_send_failed = 0
139
+ messages_received = 0
140
+ messages_unreceived = 0
141
+ hdrh_publish = nil
142
+ hdrh_receive = nil
143
+ @results.each do |url, data|
144
+ channels += data["channels"]
145
+ runtime << data["run_time_sec"]
146
+ subscribers += data["subscribers"]
147
+ message_length << data["message_length"]
148
+ messages_sent += data["messages"]["sent"]
149
+ messages_send_confirmed += data["messages"]["send_confirmed"]
150
+ messages_send_unconfirmed += data["messages"]["send_unconfirmed"]
151
+ messages_send_failed += data["messages"]["send_failed"]
152
+ messages_received += data["messages"]["received"]
153
+ messages_unreceived += data["messages"]["unreceived"]
154
+
155
+ if data["message_publishing_histogram"]
156
+ hdrh = HDRHistogram.unserialize(data["message_publishing_histogram"], unit: :ms, multiplier: 0.001)
157
+ if hdrh_publish
158
+ hdrh_publish.merge! hdrh
159
+ else
160
+ hdrh_publish = hdrh
161
+ end
162
+ end
163
+ if data["message_delivery_histogram"]
164
+ hdrh = HDRHistogram.unserialize(data["message_delivery_histogram"], unit: :ms, multiplier: 0.001)
165
+ if hdrh_receive
166
+ hdrh_receive.merge! hdrh
167
+ else
168
+ hdrh_receive = hdrh
169
+ end
170
+ end
171
+ end
172
+
173
+ message_length.uniq!
174
+ runtime.uniq!
175
+
176
+ fmt = <<-END.gsub(/^ {6}/, '')
177
+ Nchan servers: %d
178
+ runtime: %s
179
+ channels: %d
180
+ subscribers: %d
181
+ subscribers per channel: %.1f
182
+ messages:
183
+ length: %s
184
+ sent: %d
185
+ send_confirmed: %d
186
+ send_unconfirmed: %d
187
+ send_failed: %d
188
+ received: %d
189
+ unreceived: %d
190
+ send rate: %.3f/sec
191
+ receive rate: %.3f/sec
192
+ send rate per channel: %.3f/min
193
+ receive rate per subscriber: %.3f/min
194
+ END
195
+ out = fmt % [
196
+ @n, runtime.join(","), channels, subscribers, subscribers.to_f/channels,
197
+ message_length.join(","), messages_sent, messages_send_confirmed, messages_send_unconfirmed, messages_send_failed,
198
+ messages_received, messages_unreceived,
199
+ messages_sent.to_f/runtime.max,
200
+ messages_received.to_f/runtime.max,
201
+ (messages_sent.to_f* 60)/(runtime.max*channels),
202
+ (messages_received.to_f * 60)/(runtime.max * subscribers)
203
+ ]
204
+
205
+ out << hdrhistogram_stats("message publishing latency", hdrh_publish) if hdrh_publish
206
+ out << hdrhistogram_stats("message delivery latency", hdrh_receive) if hdrh_receive
207
+
208
+ puts out
209
+ end
210
+ end
211
+
212
+ benchan = Benchan.new urls
213
+ benchan.run
214
+ benchan.results
data/exe/nchan-pub ADDED
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env ruby
2
+ require 'securerandom'
3
+ require 'nchan_tools/pubsub'
4
+ require "optparse"
5
+ server= "localhost:8082"
6
+ msg=false
7
+ loop=false
8
+ repeat_sec=0.5
9
+ content_type=nil
10
+ eventsource_event=nil
11
+ msg_gen = false
12
+ on_response=Proc.new {}
13
+ method=:POST
14
+ runonce=false
15
+ accept = nil
16
+ timeout = 3
17
+ verbose = nil
18
+ url=nil
19
+ websocket = nil
20
+
21
+ opt=OptionParser.new do |opts|
22
+ opts.on("-s", "--server SERVER (#{server})", "server and port.") {|v| server=v}
23
+ opts.on("--url FULL_URL", "publishing url") {|v| url=v}
24
+ opts.on("-v", "--verbose", "Blabberhttp"){verbose=true}
25
+ opts.on("-l", "--loop [SECONDS]", "re-send message every N seconds (#{repeat_sec})") do |v|
26
+ loop=true
27
+ repeat_sec=Float(v) unless v.nil?
28
+ end
29
+ opts.on("-M", "--method [#{method}]", "method for request to server"){|v| method= v.upcase.to_sym}
30
+ opts.on("-m", "--message MSG", "publish this message instead of prompting"){|v| msg=v}
31
+ opts.on("-1", "--once", "run once then exit"){runonce=true}
32
+ opts.on("-a", "--accept TYPE", "set Accept header"){|v| accept=v}
33
+ opts.on("-c", "--content-type TYPE", "set content-type for all messages"){|v| content_type=v}
34
+ opts.on( "--eventsource_event EVENT", "event: line for eversource subscribers"){|v| eventsource_event=v}
35
+ opts.on("-e", "--eval RUBY_BLOCK", '{|n| "message #{n}" }'){|v| msg_gen = eval " Proc.new #{v} "}
36
+ opts.on("-w", "--websocket", "user websocket to publish"){websocket = true}
37
+ opts.on("-d", "--delete", "delete channel via a DELETE request"){method = :DELETE}
38
+ opts.on("-p", "--put", "create channel without submitting message"){method = :PUT}
39
+ opts.on("-t", "--timeout SECONDS", "publishing timeout (sec). default #{timeout} sec"){|v| timeout = v.to_i}
40
+ opts.on("-r", "--response", 'Show response code and body') do
41
+ on_response = Proc.new do |code, body|
42
+ puts code
43
+ puts body
44
+ end
45
+ end
46
+ end
47
+ opt.banner="Usage: pub.rb [options] url"
48
+ opt.parse!
49
+
50
+ url ||= "http://#{server}#{ARGV.last}"
51
+
52
+ puts "Publishing to #{url}."
53
+
54
+
55
+ loopmsg=("\r"*20) + "sending message #"
56
+
57
+ pub = Publisher.new url, nostore: true, timeout: timeout, verbose: verbose, websocket: websocket
58
+ pub.accept=accept
59
+ pub.nofail=true
60
+ repeat=true
61
+ i=1
62
+ if loop then
63
+ puts "Press enter to send message."
64
+ end
65
+ while repeat do
66
+ if msg or msg_gen
67
+ if loop
68
+ sleep repeat_sec
69
+ print "#{loopmsg} #{i}"
70
+ elsif !runonce
71
+ STDIN.gets
72
+ end
73
+ if msg
74
+ pub.submit msg, method, content_type, eventsource_event, &on_response
75
+ elsif msg_gen
76
+ this_msg = msg_gen.call(i).to_s
77
+ puts this_msg
78
+ pub.submit this_msg, method, content_type, eventsource_event, &on_response
79
+ end
80
+ else
81
+ if loop
82
+ puts "Can't repeat with custom message. use -m option"
83
+ end
84
+ puts "Enter message, press enter twice."
85
+ message = ""
86
+ while((line = STDIN.gets) != "\n") do #doesn't work when there are parameters. wtf?
87
+ message << line
88
+ end
89
+ message=message[0..-2] #remove trailing newline
90
+
91
+ pub.submit message, method, content_type, eventsource_event, &on_response
92
+ puts ""
93
+ end
94
+ i+=1
95
+ exit 0 if runonce
96
+ end
data/exe/nchan-sub ADDED
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'securerandom'
4
+ require 'nchan_tools/pubsub'
5
+ require "optparse"
6
+ require 'timers'
7
+
8
+ server= "localhost:8082"
9
+ par=1
10
+
11
+ opt = {
12
+ timeout: 60,
13
+ quit_message: 'FIN',
14
+ client: :longpoll,
15
+ extra_headers: nil,
16
+ nostore: true,
17
+ http2: false
18
+ }
19
+
20
+ no_message=false
21
+ print_content_type = false
22
+ show_id=false
23
+ origin = nil
24
+
25
+ permessage_deflate = false
26
+ ws_meta_subprotocol = false
27
+
28
+ url = nil
29
+ sub = nil
30
+
31
+ opt_parser=OptionParser.new do |opts|
32
+ opts.on("-s", "--server SERVER (#{server})", "server and port."){|v| server=v}
33
+ opts.on("-p", "--parallel NUM (#{par})", "number of parallel clients"){|v| par = v.to_i}
34
+ opts.on("-t", "--timeout SEC (#{opt[:timeout]})", "Long-poll timeout"){|v| opt[:timeout] = v}
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|
37
+ opt[:client] = v.to_sym
38
+ end
39
+ opts.on("--content-type", "show received content-type"){|v| print_content_type = true}
40
+ opts.on("-i", "--id", "Print message id (last-modified and etag headers)."){|v| show_id = true}
41
+ opts.on("-n", "--no-message", "Don't output retrieved message."){|v| no_message = true}
42
+ opts.on("--origin STR", "Set Origin header if appplicable."){|v| origin = v}
43
+ opts.on("--url URL", "full subscriber url") do |v|
44
+ url = v
45
+ end
46
+ opts.on("--http2", "use HTTP/2"){opt[:http2] = true}
47
+
48
+ opts.on("--websocket-permessage-deflate", "Try to use the websocket permessage-deflate extension."){opt[:permessage_deflate]=true}
49
+ opts.on("--websocket-permessage-deflate-max-window-bits NUM", "max-window-bits permessage-deflate setting") {|n|opt[:permessage_deflate_max_window_bits]=n.to_i}
50
+ opts.on("--websocket-permessage-deflate-server-max-window-bits NUM", "server-max-window-bits permessage-deflate setting") {|n|opt[:permessage_deflate_server_max_window_bits]=n.to_i}
51
+ opts.on("--websocket-meta-subprotocol", "Use the ws+meta.nchan websocket subprotocol"){opt[:subprotocol]="ws+meta.nchan"}
52
+
53
+ opts.on("-v", "--verbose", "somewhat rather extraneously wordful output") do
54
+ opt[:verbose] = true
55
+ Typhoeus::Config.verbose=true
56
+ end
57
+ end
58
+ opt_parser.banner="Usage: sub.rb [options] url"
59
+ opt_parser.parse!
60
+
61
+ url ||= "#{opt[:http2] ? 'h2' : 'http'}://#{server}#{ARGV.last}"
62
+
63
+ puts "Subscribing #{par} #{opt[:client]} client#{par!=1 ? "s":""} to #{url}."
64
+ puts "Timeout: #{opt[:timeout]}sec, quit msg: #{opt[:quit_message]}"
65
+
66
+ if origin
67
+ opt[:extra_headers] ||= {}
68
+ opt[:extra_headers]['Origin'] = origin
69
+ end
70
+
71
+ sub = Subscriber.new url, par, opt
72
+
73
+
74
+ NOMSGF="\r"*30 + "Received message %i, len:%i"
75
+ if no_message
76
+ class OutputTimer
77
+ include Celluloid
78
+ attr_reader :fired, :timer
79
+
80
+ def initialize(interval = 0.5)
81
+ @count = 0
82
+ @last_msg_length = 0
83
+ @timer = every(interval) do
84
+ printf NOMSGF, @count, @last_msg_length
85
+ end
86
+ end
87
+
88
+ def incoming(msg)
89
+ @count += 1
90
+ @last_msg_length = msg.message.length
91
+ end
92
+ end
93
+
94
+ output_timer = OutputTimer.new
95
+ end
96
+
97
+ sub.on_message do |msg|
98
+ if no_message
99
+ output_timer.incoming(msg)
100
+ else
101
+ if msg.content_type
102
+ out = "(#{msg.content_type}) #{msg}"
103
+ else
104
+ out = msg.to_s
105
+ end
106
+ if show_id
107
+ out = "<#{msg.id}> #{out}"
108
+ end
109
+ puts out
110
+ end
111
+ end
112
+
113
+ sub.on_failure do |err_msg|
114
+ if Subscriber::IntervalPollClient === sub.client
115
+ unless err_msg.match(/\(code 304\)/)
116
+ false
117
+ end
118
+ else
119
+ false
120
+ end
121
+ end
122
+
123
+ sub.run
124
+ begin
125
+ sub.wait
126
+ rescue Interrupt => e
127
+ #do nothing
128
+ end
129
+
130
+ output_timer.terminate if output_timer
131
+
132
+
133
+ if sub.errors.count > 0
134
+ puts "Errors:"
135
+ sub.errors.each do |err|
136
+ puts err
137
+ end
138
+ exit 1
139
+ end