Skip to content

Commit

Permalink
feat: 🤖✨ Add Render platform support (#142)
Browse files Browse the repository at this point in the history
We've extracted everything Heroku-specific into common language that works for both Render and Heroku. We also no longer require a `JUDOSCALE_URL` env var if `RENDER_INSTANCE_ID` is present.
  • Loading branch information
jon-sully authored Apr 19, 2023
1 parent 6c91e1d commit 81f283e
Show file tree
Hide file tree
Showing 47 changed files with 282 additions and 142 deletions.
4 changes: 2 additions & 2 deletions judoscale-delayed_job/judoscale-delayed_job.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ require "judoscale/delayed_job/version"
Gem::Specification.new do |spec|
spec.name = "judoscale-delayed_job"
spec.version = Judoscale::DelayedJob::VERSION
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva"]
spec.email = ["adam@adamlogic.com"]
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva", "Jon Sullivan"]
spec.email = ["hello@judoscale.com"]

spec.summary = "This gem provides DelayedJob integration with the Judoscale autoscaling add-on for Heroku."
spec.homepage = "https://judoscale.com"
Expand Down
4 changes: 2 additions & 2 deletions judoscale-delayed_job/rails-autoscale-delayed_job.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ require "judoscale/delayed_job/version"
Gem::Specification.new do |spec|
spec.name = "rails-autoscale-delayed_job"
spec.version = Judoscale::DelayedJob::VERSION
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva"]
spec.email = ["adam@adamlogic.com"]
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva", "Jon Sullivan"]
spec.email = ["hello@judoscale.com"]

spec.summary = "This gem provides DelayedJob integration with the Judoscale autoscaling add-on for Heroku."
spec.homepage = "https://judoscale.com"
Expand Down
4 changes: 2 additions & 2 deletions judoscale-good_job/judoscale-good_job.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ require "judoscale/good_job/version"
Gem::Specification.new do |spec|
spec.name = "judoscale-good_job"
spec.version = Judoscale::GoodJob::VERSION
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva"]
spec.email = ["adam@adamlogic.com"]
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva", "Jon Sullivan"]
spec.email = ["hello@judoscale.com"]

spec.summary = "This gem provides GoodJob integration with the Judoscale autoscaling add-on for Heroku."
spec.homepage = "https://judoscale.com"
Expand Down
4 changes: 2 additions & 2 deletions judoscale-good_job/rails-autoscale-good_job.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ require "judoscale/good_job/version"
Gem::Specification.new do |spec|
spec.name = "rails-autoscale-good_job"
spec.version = Judoscale::GoodJob::VERSION
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva"]
spec.email = ["adam@adamlogic.com"]
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva", "Jon Sullivan"]
spec.email = ["hello@judoscale.com"]

spec.summary = "This gem provides GoodJob integration with the Judoscale autoscaling add-on for Heroku."
spec.homepage = "https://judoscale.com"
Expand Down
4 changes: 2 additions & 2 deletions judoscale-que/judoscale-que.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ require "judoscale/que/version"
Gem::Specification.new do |spec|
spec.name = "judoscale-que"
spec.version = Judoscale::Que::VERSION
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva"]
spec.email = ["adam@adamlogic.com"]
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva", "Jon Sullivan"]
spec.email = ["hello@judoscale.com"]

spec.summary = "This gem provides Que integration with the Judoscale autoscaling add-on for Heroku."
spec.homepage = "https://judoscale.com"
Expand Down
4 changes: 2 additions & 2 deletions judoscale-que/rails-autoscale-que.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ require "judoscale/que/version"
Gem::Specification.new do |spec|
spec.name = "rails-autoscale-que"
spec.version = Judoscale::Que::VERSION
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva"]
spec.email = ["adam@adamlogic.com"]
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva", "Jon Sullivan"]
spec.email = ["hello@judoscale.com"]

spec.summary = "This gem provides Que integration with the Judoscale autoscaling add-on for Heroku."
spec.homepage = "https://judoscale.com"
Expand Down
4 changes: 2 additions & 2 deletions judoscale-rack/judoscale-rack.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ require "judoscale/rack/version"
Gem::Specification.new do |spec|
spec.name = "judoscale-rack"
spec.version = Judoscale::Rack::VERSION
spec.authors = ["Adam McCrea"]
spec.email = ["adam@adamlogic.com"]
spec.authors = ["Adam McCrea", "Jon Sullivan"]
spec.email = ["hello@judoscale.com"]

spec.summary = "This gem provides Rack app integration with the Judoscale autoscaling add-on for Heroku."
spec.homepage = "https://judoscale.com"
Expand Down
4 changes: 2 additions & 2 deletions judoscale-rack/rails-autoscale-rack.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ require "judoscale/rack/version"
Gem::Specification.new do |spec|
spec.name = "rails-autoscale-rack"
spec.version = Judoscale::Rack::VERSION
spec.authors = ["Adam McCrea"]
spec.email = ["adam@adamlogic.com"]
spec.authors = ["Adam McCrea", "Jon Sullivan"]
spec.email = ["hello@judoscale.com"]

spec.summary = "This gem provides Rack app integration with the Judoscale autoscaling add-on for Heroku."
spec.homepage = "https://judoscale.com"
Expand Down
4 changes: 2 additions & 2 deletions judoscale-rails/judoscale-rails.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ require "judoscale/rails/version"
Gem::Specification.new do |spec|
spec.name = "judoscale-rails"
spec.version = Judoscale::Rails::VERSION
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva"]
spec.email = ["adam@adamlogic.com"]
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva", "Jon Sullivan"]
spec.email = ["hello@judoscale.com"]

spec.summary = "This gem provides Ruby on Rails integration with the Judoscale autoscaling add-on for Heroku."
spec.homepage = "https://judoscale.com"
Expand Down
4 changes: 2 additions & 2 deletions judoscale-rails/rails-autoscale-web.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ require "judoscale/rails/version"
Gem::Specification.new do |spec|
spec.name = "rails-autoscale-web"
spec.version = Judoscale::Rails::VERSION
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva"]
spec.email = ["adam@adamlogic.com"]
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva", "Jon Sullivan"]
spec.email = ["hello@judoscale.com"]

spec.summary = "This gem provides Ruby on Rails integration with the Judoscale autoscaling add-on for Heroku."
spec.homepage = "https://judoscale.com"
Expand Down
4 changes: 2 additions & 2 deletions judoscale-resque/judoscale-resque.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ require "judoscale/resque/version"
Gem::Specification.new do |spec|
spec.name = "judoscale-resque"
spec.version = Judoscale::Resque::VERSION
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva"]
spec.email = ["adam@adamlogic.com"]
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva", "Jon Sullivan"]
spec.email = ["hello@judoscale.com"]

spec.summary = "This gem provides Resque integration with the Judoscale autoscaling add-on for Heroku."
spec.homepage = "https://judoscale.com"
Expand Down
4 changes: 2 additions & 2 deletions judoscale-resque/rails-autoscale-resque.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ require "judoscale/resque/version"
Gem::Specification.new do |spec|
spec.name = "rails-autoscale-resque"
spec.version = Judoscale::Resque::VERSION
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva"]
spec.email = ["adam@adamlogic.com"]
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva", "Jon Sullivan"]
spec.email = ["hello@judoscale.com"]

spec.summary = "This gem provides Resque integration with the Judoscale autoscaling add-on for Heroku."
spec.homepage = "https://judoscale.com"
Expand Down
4 changes: 2 additions & 2 deletions judoscale-ruby/judoscale-ruby.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ require "judoscale/version"
Gem::Specification.new do |spec|
spec.name = "judoscale-ruby"
spec.version = Judoscale::VERSION
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva"]
spec.email = ["adam@adamlogic.com"]
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva", "Jon Sullivan"]
spec.email = ["hello@judoscale.com"]

spec.summary = "This gem works with the Judoscale Heroku add-on to automatically scale your web and worker dynos."
spec.homepage = "https://judoscale.com"
Expand Down
59 changes: 41 additions & 18 deletions judoscale-ruby/lib/judoscale/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,35 @@

module Judoscale
class Config
class Dyno
attr_reader :name, :num

def initialize(dyno_string)
@name, @num = dyno_string.to_s.split(".")
@num = @num.to_i
class RuntimeContainer
# E.g.:
# (Heroku) => "worker_fast", "3"
# (Render) => "srv-cfa1es5a49987h4vcvfg", "5497f74465-m5wwr", "web" (or "worker", "pserv", "cron", "static")
def initialize(service_name = nil, instance = nil, service_type = nil)
@service_name = service_name
@instance = instance
@service_type = service_type
end

def to_s
"#{name}.#{num}"
# heroku: 'worker_fast.5'
# render: 'srv-cfa1es5a49987h4vcvfg.5497f74465-m5wwr'
"#{@service_name}.#{@instance}"
end

def web?
# NOTE: Heroku isolates 'web' as the required _name_ for its web process
# type, Render exposes the actual service type more explicitly
@service_name == "web" || @service_type == "web"
end

# Since Heroku exposes ordinal dyno 'numbers', we can tell if the current
# instance is redundant (and thus skip collecting some metrics sometimes)
# We don't have a means of determining that on Render though — so every
# instance must be considered non-redundant
def redundant_instance?
instance_is_number = Integer(@instance, exception: false)
instance_is_number && instance_is_number != 1
end
end

Expand Down Expand Up @@ -66,28 +85,36 @@ def self.expose_adapter_config(config_instance)
end
end

attr_accessor :api_base_url, :report_interval_seconds, :max_request_size_bytes, :logger, :log_tag
attr_reader :dyno, :log_level
attr_accessor :api_base_url, :report_interval_seconds,
:max_request_size_bytes, :logger, :log_tag, :current_runtime_container
attr_reader :log_level

def initialize
reset
end

def reset
# Allow the API URL to be configured - needed for testing.
@api_base_url = ENV["JUDOSCALE_URL"] || ENV["RAILS_AUTOSCALE_URL"]
@log_tag = "Judoscale"
self.dyno = ENV["DYNO"]
@max_request_size_bytes = 100_000 # ignore request payloads over 100k since they skew the queue times
@report_interval_seconds = 10

self.log_level = ENV["JUDOSCALE_LOG_LEVEL"] || ENV["RAILS_AUTOSCALE_LOG_LEVEL"]
@logger = ::Logger.new($stdout)

self.class.adapter_configs.each(&:reset)
end

def dyno=(dyno_string)
@dyno = Dyno.new(dyno_string)
if ENV["RENDER_INSTANCE_ID"]
instance = ENV["RENDER_INSTANCE_ID"].delete_prefix(ENV["RENDER_SERVICE_ID"]).delete_prefix("-")
@current_runtime_container = RuntimeContainer.new ENV["RENDER_SERVICE_ID"], instance, ENV["RENDER_SERVICE_TYPE"]
@api_base_url ||= "https://adapter.judoscale.com/api/#{ENV["RENDER_SERVICE_ID"]}"
elsif ENV["DYNO"]
service_name, instance = ENV["DYNO"].split "."
@current_runtime_container = RuntimeContainer.new service_name, instance
else
# unsupported platform? Don't want to leave @current_runtime_container nil though
@current_runtime_container = RuntimeContainer.new
end
end

def log_level=(new_level)
Expand All @@ -105,10 +132,6 @@ def as_json
}.merge!(adapter_configs_json)
end

def to_s
"#{@dyno}##{Process.pid}"
end

def ignore_large_requests?
@max_request_size_bytes
end
Expand Down
3 changes: 1 addition & 2 deletions judoscale-ruby/lib/judoscale/job_metrics_collector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ module Judoscale
class JobMetricsCollector < MetricsCollector
include Judoscale::Logger

# It's redundant to report these metrics from every dyno, so only report from the first one.
def self.collect?(config)
config.dyno.num == 1 && adapter_config.enabled
!config.current_runtime_container.redundant_instance? && adapter_config.enabled
end

def self.adapter_name
Expand Down
2 changes: 1 addition & 1 deletion judoscale-ruby/lib/judoscale/report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def initialize(adapters, config, metrics = [])

def as_json
{
dyno: config.dyno,
container: config.current_runtime_container,
pid: Process.pid,
config: config.as_json,
adapters: adapters.reduce({}) { |hash, adapter| hash.merge!(adapter.as_json) },
Expand Down
2 changes: 1 addition & 1 deletion judoscale-ruby/lib/judoscale/reporter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def start!(config, adapters)
metrics_collectors_classes.compact!

if metrics_collectors_classes.empty?
logger.info "Reporter not started: no metrics need to be collected on this dyno"
logger.info "Reporter not started: no metrics need to be collected in this process"
return
end

Expand Down
4 changes: 3 additions & 1 deletion judoscale-ruby/lib/judoscale/web_metrics_collector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@

module Judoscale
class WebMetricsCollector < MetricsCollector
# NOTE: We collect metrics on all running web processes since they
# all receive and handle requests independently
def self.collect?(config)
config.dyno.name == "web"
config.current_runtime_container.web?
end

def collect
Expand Down
4 changes: 2 additions & 2 deletions judoscale-ruby/rails-autoscale-core.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ require "judoscale/version"
Gem::Specification.new do |spec|
spec.name = "rails-autoscale-core"
spec.version = Judoscale::VERSION
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva"]
spec.email = ["adam@adamlogic.com"]
spec.authors = ["Adam McCrea", "Carlos Antonio da Silva", "Jon Sullivan"]
spec.email = ["hello@judoscale.com"]

spec.summary = "This gem works with the Judoscale Heroku add-on to automatically scale your web and worker dynos."
spec.homepage = "https://judoscale.com"
Expand Down
44 changes: 39 additions & 5 deletions judoscale-ruby/test/config_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,43 @@
module Judoscale
describe Config do
it "initializes the config from default heroku ENV vars and other sensible defaults" do
use_env "DYNO" => "web.1", "JUDOSCALE_URL" => "https://example.com" do
env = {
"DYNO" => "web.1",
"JUDOSCALE_URL" => "https://example.com"
}

use_env env do
config = Config.instance
_(config.api_base_url).must_equal "https://example.com"
_(config.dyno.to_s).must_equal "web.1"
_(config.current_runtime_container.to_s).must_equal "web.1"
_(config.log_level).must_be_nil
_(config.logger).must_be_instance_of ::Logger
_(config.max_request_size_bytes).must_equal 100_000
_(config.report_interval_seconds).must_equal 10

enabled_adapter_configs = Config.adapter_configs.select(&:enabled).map(&:identifier)
_(enabled_adapter_configs).must_equal %i[test_job_config]

enabled_adapter_configs.each do |adapter_name|
adapter_config = config.public_send(adapter_name)
_(adapter_config.enabled).must_equal true
_(adapter_config.max_queues).must_equal 20
_(adapter_config.track_busy_jobs).must_equal false
end
end
end

it "initializes the config from default render ENV vars and other sensible defaults" do
env = {
"RENDER_SERVICE_ID" => "srv-cfa1es5a49987h4vcvfg",
"RENDER_INSTANCE_ID" => "srv-cfa1es5a49987h4vcvfg-5497f74465-m5wwr",
"RENDER_SERVICE_TYPE" => "web"
}

use_env env do
config = Config.instance
_(config.api_base_url).must_equal "https://adapter.judoscale.com/api/srv-cfa1es5a49987h4vcvfg"
_(config.current_runtime_container.to_s).must_equal "srv-cfa1es5a49987h4vcvfg.5497f74465-m5wwr"
_(config.log_level).must_be_nil
_(config.logger).must_be_instance_of ::Logger
_(config.max_request_size_bytes).must_equal 100_000
Expand Down Expand Up @@ -37,7 +70,7 @@ module Judoscale
use_env env do
config = Config.instance
_(config.api_base_url).must_equal "https://custom.example.com"
_(config.dyno.to_s).must_equal "web.2"
_(config.current_runtime_container.to_s).must_equal "web.2"
_(config.log_level).must_equal ::Logger::Severity::DEBUG
end
end
Expand All @@ -59,7 +92,8 @@ module Judoscale
test_logger = ::Logger.new(StringIO.new)

Judoscale.configure do |config|
config.dyno = "web.3"
config.current_runtime_container = Config::RuntimeContainer.new("web", "3")

config.api_base_url = "https://block.example.com"
config.log_level = :info
config.logger = test_logger
Expand All @@ -72,7 +106,7 @@ module Judoscale

config = Config.instance
_(config.api_base_url).must_equal "https://block.example.com"
_(config.dyno.to_s).must_equal "web.3"
_(config.current_runtime_container.to_s).must_equal "web.3"
_(config.log_level).must_equal ::Logger::Severity::INFO
_(config.logger).must_equal test_logger
_(config.max_request_size_bytes).must_equal 50_000
Expand Down
Loading

0 comments on commit 81f283e

Please sign in to comment.