Files
once-campfire/bin/load
Kevin McConnell df76a227dc Hello world
First open source release of Campfire 🎉
2025-08-21 09:31:59 +01:00

191 lines
5.0 KiB
Ruby
Executable File

#!/usr/bin/env ruby
require "bundler/inline"
gemfile do
source "https://rubygems.org"
gem "base64"
gem "bcrypt_pbkdf"
gem "ed25519"
gem "sshkit"
end
require "optparse"
require "net/http"
require "uri"
class Load
include SSHKit::DSL
attr_reader :users, :setup, :host, :port, :ssh_user
attr_reader :campfire_image
def initialize(users: 1000, setup: true, host: "localhost", port: nil, ssh_user: "ubuntu")
@users = users
@setup = setup
@host = host
@ssh_user = ssh_user
@port = port || (remote? ? 80 : 3000)
@campfire_image = "localhost/campfire:latest"
end
def setup?
@setup
end
def run
configure_sshkit
run_setup if setup?
start_app
run_test
output_results
cleanup
end
def remote?
![ "localhost", "127.0.0.1" ].include?(host)
end
def ssh_host
remote? ? host : :local
end
def k6_dir
remote? ? "$PWD" : "$PWD/test/performance"
end
private
def configure_sshkit
SSHKit::Backend::Netssh.configure do |ssh|
ssh.connection_timeout = 30
ssh.ssh_options = { user: ssh_user }
end
end
def run_setup
puts "Running setup..."
load = self
os, arch = host_os_and_arch
on(:local) do
execute :docker, :build, "-t", load.campfire_image, "--platform", arch, "."
if load.remote?
execute :docker, :save, "-o", "tmp/campfire-image.tar", load.campfire_image
end
end
if remote?
on(ssh_host) do
unless execute(:docker, "-v", raise_on_non_zero_exit: false)
execute :curl, "-fsSL", "https://get.docker.com", "|", :sh
end
upload! "tmp/campfire-image.tar", "campfire-image.tar"
upload! "test/performance/chatter.js", "chatter.js"
upload! "test/performance/cookies.txt", "cookies.txt"
execute :docker, :load, "-i", "campfire-image.tar"
end
end
end
def host_os_and_arch
os = arch = nil
on(ssh_host) do
os = capture("uname -s").strip
arch = capture("uname -m").strip == "x86_64" ? "linux/amd64" : "linux/arm64"
end
[os, arch]
end
def start_app
puts "Starting app on #{host}..."
load = self
on(ssh_host) do
execute :docker, :rm, "-f", :campfire_load, raise_on_non_zero_exit: false
execute :docker, :run, "-d", "--rm", "--name", :campfire_load, "-p", "#{load.port}:80", "-e", "SECRET_KEY_BASE=dummy", "-e RAILS_ENV=performance", load.campfire_image
end
wait_for_app_to_start
end
def wait_for_app_to_start
timeout_at = Time.now + 60
uri = URI("http://#{host}:#{port}")
loop do
sleep 1
puts "Waiting for app to start..."
raise "App not started after 60 seconds" if Time.now > timeout_at
break if Net::HTTP.get_response(uri).code.to_i < 400
sleep 1
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, EOFError
end
end
def run_test
puts "Running load test..."
load = self
on(ssh_host) do |host|
execute :docker, :rm, "-f", :k6, raise_on_non_zero_exit: false
execute :docker, :run, "--name", :k6, "-u \"$(id -u):$(id -g)\"", "-e HOST=#{load.host}", "-e PORT=#{load.port}", "-e USERS=#{load.users}", "-v", "#{load.k6_dir}:/src", "grafana/k6", :run, "/src/chatter.js"
if host.local?
execute :docker, :logs, :k6, "2>", "tmp/k6.log"
else
execute :docker, :logs, :k6, "2>", "k6.log"
download! "k6.log", "tmp/k6.log"
end
end
end
def output_results
puts "Outputting results..."
on(:local) do
puts "Connections/s"
puts "-------------"
puts capture(:cat, "tmp/k6.log", "|", "grep 'Subscription confirmed' | awk '{print $1}' | sort| uniq -c | sed '1d; $d' | awk '{sum += $1; count += 1; max = (max > $1) ? max : $1} END {print \"Average:\",sum/count/6,\"Max:\",max/6}'")
puts
puts "Messages/s"
puts "--------------"
puts capture(:cat, "tmp/k6.log", "|", "grep 'Message received' | awk '{print $1}' | sort | uniq -c | sed '1d; $d' | awk '{sum += $1; count += 1; max = (max > $1) ? max : $1} END {print \"Average:\",sum/count,\"Max:\",max}'")
end
end
def cleanup
on(ssh_host) do |host|
execute :docker, :rm, "-f", :campfire_load, :k6
end
end
end
options = {}
OptionParser.new do |opts|
opts.banner = "Usage: #{$0} [options]"
opts.on("-u", "--users USERS", "User count") do |value|
options[:users] = value&.to_i
end
opts.on("-S", "--skip-setup", "Skip setup") do
options[:setup] = false
end
opts.on("-h", "--host host", "Host to run the app on") do |host|
options[:host] = host
end
opts.on("--ssh-user ssh_user", "User to ssh to remote hosts") do |ssh_user|
options[:ssh_user] = ssh_user
end
opts.on("-p", "--port PORT", "Application port ") do |port|
options[:port] = port
end
opts.on("--help", "Prints this message") do
puts opts
exit 0
end
end.parse!
Load.new(**options).run