nchan_tools 0.1.1

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