Skip to content

Instantly share code, notes, and snippets.

@palkan
Created November 9, 2016 11:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save palkan/3a77eabb7229d763a53d1d709b9d92e0 to your computer and use it in GitHub Desktop.
Save palkan/3a77eabb7229d763a53d1d709b9d92e0 to your computer and use it in GitHub Desktop.
ActionCable Streaming Benchmark
# rubocop disable:all
begin
require 'bundler/inline'
rescue LoadError => e
$stderr.puts 'Bundler version 1.10 or later is required. Please update your Bundler'
raise e
end
gemfile(true) do
source 'https://rubygems.org'
gem 'actioncable', '~>5.0'
gem 'benchmark-ips'
end
require 'json'
require 'action_cable'
require 'logger'
require 'benchmark/ips'
require 'benchmark'
class Measure
def self.run(gc: :enable)
if gc == :disable
GC.disable
elsif gc == :enable
GC.start
end
memory_before = `ps -o rss= -p #{Process.pid}`.to_i / 1024
gc_stat_before = GC.stat
time = Benchmark.realtime do
yield
end
gc_stat_after = GC.stat
GC.start if gc == :enable
memory_after = `ps -o rss= -p #{Process.pid}`.to_i / 1024
$stdout.puts(
{
gc: gc,
time: time.round(2),
gc_major_count: gc_stat_after[:major_gc_count].to_i - gc_stat_before[:major_gc_count].to_i,
gc_minor_count: gc_stat_after[:minor_gc_count].to_i - gc_stat_before[:minor_gc_count].to_i,
memory: "%d MB" % (memory_after - memory_before)
}.to_json
)
end
end
class FakeSocket
def transmit(msg)
msg.to_s
end
end
class FakeConnection < ActionCable::Connection::Base
def initialize
@coder = ActiveSupport::JSON
end
def websocket
@websocket ||= FakeSocket.new
end
def worker_pool
@worker_pool ||= ActionCable::Server::Worker.new(max_size: 4)
end
end
class SimpleSubscriberMap < ActionCable::SubscriptionAdapter::SubscriberMap
def invoke_callback(callback, message)
callback[:connection].worker_pool.async_invoke(
self,
:transmit,
message,
connection: callback[:connection],
)
end
def transmit(message, connection:)
connection.send(:websocket).transmit(
"{\"identifier\": #{callback[:id]},\"message\": #{message}}"
)
end
end
class DecodingSubscriberMap < SimpleSubscriberMap
def transmit(message, connection:)
connection.transmit(
identifier: callback[:id],
message: ActiveSupport::JSON.decode(message)
)
end
end
class FakeChannel < ActionCable::Channel::Base
def delegate_connection_identifiers
# noop
end
def subscribe_to_channel
# noop
end
end
decoding_map = DecodingSubscriberMap.new
simple_map = SimpleSubscriberMap.new
base_map = ActionCable::SubscriptionAdapter::SubscriberMap.new
message = { text: "ActionCable should be better" }.to_json
N = 100
Benchmark.ips do |x|
# Very simple transmitter: just broadcast message as is
x.report('SimpleSubscriberMap') do
connection = FakeConnection.new
simple_handler = { connection: connection, id: 'FakeChannel' }
N.times do
simple_map.invoke_callback(simple_handler, message)
end
connection.worker_pool.executor.shutdown
connection.worker_pool.executor.wait_for_termination
end
# Simple transmitter with JSON round-trip
x.report('DecodingSubscriberMap') do
connection = FakeConnection.new
simple_handler = { connection: connection, id: 'FakeChannel' }
N.times do
decoding_map.invoke_callback(simple_handler, message)
end
connection.worker_pool.executor.shutdown
connection.worker_pool.executor.wait_for_termination
end
# Current ActionCable callback hell implementation
x.report('SubscriberMap') do
connection = FakeConnection.new
channel = FakeChannel.new(connection, 'FakeChannel')
callback_handler = channel.send(:worker_pool_stream_handler, 'test', nil)
N.times do
base_map.invoke_callback(callback_handler, message)
end
connection.worker_pool.executor.shutdown
connection.worker_pool.executor.wait_for_termination
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment