neetodeploy-autoscale 1.0.5 → 2.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d2423b21c7c2ec87b65e527b09beb49b0a2ea39c9265dd3f28f8bb141a7734c9
4
- data.tar.gz: 6c3f665ba9c00baca79964b53ad9be4df629ccbe7e495f56c2dfce704ac5ae77
3
+ metadata.gz: 37bcdd5f87684bd29d215c8ed744b8ff6963a6a57d1af07c5ddadc407f4462d5
4
+ data.tar.gz: 05fac864a8cca54ac8773e6ec96773d4918961c32fdf1b27ff61049d34241c7f
5
5
  SHA512:
6
- metadata.gz: c672dd4ed97a3367e5e74185c31de1ba5c6d39d1068244f1bbba23330142a5c97396514e4886157c6eb89feda1ca2b425e2c58db60533d314eb6029aa27417ac
7
- data.tar.gz: d3ae69543bbe75523d433305baa081e4369cebc6ba3f893604b4e349a1230827b86014e9182576601e217a3ecf699368ee6494e58191c3380e34dcaf407a24a5
6
+ metadata.gz: cf13f8872227a9e0685460a13cbddeab3d10f5da67c55f96bd533faddb85cb153ded05cebd523d29d83690d09317999dda2fc094aa06b9bcc9e77306bb322603
7
+ data.tar.gz: a8809427527fe868c571ee014b79e242379f1ee9ff432e323dfaed5b8ede759f0fd75ca67dccd610bd8c7eadfe358e3415a3f1c4c66ed2d074e1e3073f8de353
@@ -0,0 +1,19 @@
1
+ module Neetodeploy
2
+ class Config
3
+ include Singleton
4
+
5
+ attr_accessor :disable_auto_scale_gem, :app_name, :metrics_server_url, :metrics_server_auth_token, :report_interval_seconds
6
+
7
+ def initialize
8
+ @disable_auto_scale_gem = ENV["DISABLE_NEETO_DEPLOY_AUTOSCALE"]
9
+ @app_name = ENV["NEETODEPLOY_APP_NAME"]
10
+ @metrics_server_url = "http://nd-queue-time-exporter-web-deployment:3000/metrics"
11
+ @metrics_server_auth_token = "K0An3O3MSyEEMTCnRd1IHgGjdGQkzy"
12
+ @report_interval_seconds = 10
13
+ end
14
+
15
+ def gem_disabled?
16
+ disable_auto_scale_gem == "true"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ require "logger"
2
+
3
+ module NeetoDeploy
4
+ module Logger
5
+ def self.logger
6
+ @logger ||= ::Logger.new(STDOUT).tap do |log|
7
+ log.formatter = proc { |severity, _datetime, _progname, msg| "[neetodeploy-autoscale] #{severity}: #{msg}\n" }
8
+ end
9
+ end
10
+
11
+ def logger
12
+ NeetoDeploy::Logger.logger
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,26 @@
1
+ require "neetodeploy/autoscale/config"
2
+
3
+ module Neetodeploy
4
+ class Metrics
5
+ def initialize(env, config = Config.instance)
6
+ @config = config
7
+ @request_start_header = env["HTTP_X_REQUEST_START"].to_i
8
+ @network_time = env["puma.request_body_wait"].to_i
9
+ end
10
+
11
+ def ignore?
12
+ @config.gem_disabled?
13
+ end
14
+
15
+ def queue_time
16
+ return if @request_start_header.zero?
17
+
18
+ time_now = Time.now.to_f * 1000
19
+
20
+ queue_time = (time_now - @request_start_header).round
21
+ queue_time -= @network_time
22
+
23
+ queue_time.positive? ? queue_time : nil
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+ require "neetodeploy/autoscale/metrics_store"
5
+ require "neetodeploy/autoscale/reporter"
6
+ require "neetodeploy/autoscale/logger"
7
+
8
+ module Neetodeploy
9
+ class MetricsCollector
10
+ include Singleton
11
+ include NeetoDeploy::Logger
12
+
13
+ def self.start
14
+ instance.start! unless instance.running?
15
+ end
16
+
17
+ def start!
18
+ logger.info("Starting background worker to collect metrics")
19
+ @pid = Process.pid
20
+ start_thread_with_collector_loop
21
+ end
22
+
23
+ def stop!
24
+ @thread&.terminate
25
+ @thread = nil
26
+ @pid = nil
27
+ end
28
+
29
+ def running?
30
+ @pid == Process.pid and @thread.alive?
31
+ end
32
+
33
+ def start_thread_with_collector_loop(config = Config.instance)
34
+ @thread = Thread.new do
35
+ metrics_store = MetricsStore.instance
36
+ loop do
37
+ run_metrics_collection(metrics_store)
38
+ multiplier = 1 - (rand / 4)
39
+ sleep config.report_interval_seconds * multiplier
40
+ end
41
+ end
42
+ end
43
+
44
+ def run_metrics_collection(metrics_store)
45
+ data = metrics_store.flush
46
+ return if data.empty?
47
+
48
+ average_queue_time = data.sum / data.size
49
+ logger.info("Reporting average queue time of #{data.size} metrics: #{average_queue_time}")
50
+ Reporter.new(average_queue_time).report
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+ require "judoscale/metric"
5
+ require "judoscale/report"
6
+
7
+ module Neetodeploy
8
+ class MetricsStore
9
+ include Singleton
10
+
11
+ attr_reader :metrics
12
+
13
+ def initialize
14
+ @metrics = []
15
+ end
16
+
17
+ def push(queue_time)
18
+ @metrics << queue_time
19
+ end
20
+
21
+ def flush
22
+ result = @metrics
23
+ @metrics = []
24
+ result
25
+ end
26
+ end
27
+ end
@@ -2,52 +2,27 @@
2
2
 
3
3
  require "net/http"
4
4
  require "time"
5
- require "neetodeploy/autoscale/worker"
5
+ require "neetodeploy/autoscale/metrics"
6
+ require "neetodeploy/autoscale/metrics_store"
7
+ require "neetodeploy/autoscale/metrics_collector"
6
8
 
7
9
  module Neetodeploy
8
10
  class Middleware
9
- attr_reader :worker
10
-
11
11
  def initialize(app)
12
12
  @app = app
13
- init_worker
14
13
  end
15
14
 
16
15
  def call(env)
17
- payload = URI.encode_www_form(
18
- app_name: ENV.to_h["NEETODEPLOY_APP_NAME"],
19
- process_type: "web",
20
- queue_time: queue_time(env)
21
- )
22
- worker.push(payload)
23
- @app.call(env)
24
- end
25
-
26
- private
27
-
28
- def started_at(env)
29
- return nil unless env["HTTP_X_REQUEST_START"].present?
30
-
31
- env["HTTP_X_REQUEST_START"].to_i
32
- end
16
+ metrics = Metrics.new(env)
17
+ queue_time = metrics.queue_time unless metrics.ignore?
18
+ MetricsCollector.start
33
19
 
34
- def network_time(env)
35
- env["puma.request_body_wait"].to_i
36
- end
37
-
38
- def queue_time(now = Time.now.to_f * 1000, env)
39
- return if started_at(env).nil?
20
+ if queue_time
21
+ store = MetricsStore.instance
22
+ store.push queue_time
23
+ end
40
24
 
41
- queue_time = (now - started_at(env)).round
42
- queue_time -= network_time(env)
43
-
44
- queue_time.positive? ? queue_time : 0
45
- end
46
-
47
- def init_worker
48
- return if @worker
49
-
50
- @worker = Neetodeploy::Worker.new
25
+ @app.call(env)
51
26
  end
52
27
  end
53
28
  end
@@ -1,17 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "neetodeploy/autoscale/middleware"
3
4
  module Neetodeploy
4
5
  class Railtie < Rails::Railtie
5
- initializer "neetodeploy.configure_rails_initialization" do
6
- insert_middleware
6
+ initializer "neetodeploy.Neetodeploy.middleware" do |app|
7
+ app.middleware.insert_before Rack::Runtime, Neetodeploy::Middleware
7
8
  end
8
9
 
9
- def insert_middleware
10
- Rails.application.middleware.use Neetodeploy::Middleware
10
+ config.after_initialize do
11
+ MetricsCollector.start unless in_rails_console_or_runner?
11
12
  end
12
13
 
13
- def use_better_errors?
14
- !Rails.env.production? and app.config.consider_all_requests_local
14
+ def in_rails_console_or_runner?
15
+ # This is gross, but we can't find a more reliable way to detect if we're in a Rails console/runner.
16
+ caller.any? { |call| call.include?("console_command.rb") || call.include?("runner_command.rb") }
15
17
  end
16
18
  end
17
19
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "time"
5
+ require "neetodeploy/autoscale/config"
6
+
7
+ module Neetodeploy
8
+ class Reporter
9
+
10
+ def initialize(queue_time, config = Config.instance )
11
+ @queue_time = queue_time.to_i
12
+ @config = config
13
+ end
14
+
15
+ def report
16
+ url = URI.parse(@config.metrics_server_url)
17
+ url.query = URI.encode_www_form(app_name: @config.app_name, process_type: "web", queue_time: @queue_time)
18
+
19
+ post = Net::HTTP::Post.new(url)
20
+ post["AuthToken"] = @config.metrics_server_auth_token
21
+
22
+ Net::HTTP.start(url.host, url.port, use_ssl: false) do |http|
23
+ http.request(post)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Neetodeploy
4
4
  module Autoscale
5
- VERSION = "1.0.5"
5
+ VERSION = "2.0.0"
6
6
  end
7
7
  end
@@ -1,33 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "neetodeploy/autoscale/version"
4
+ require "neetodeploy/autoscale/logger"
4
5
 
5
6
  module Neetodeploy
6
7
  module Autoscale
7
8
  class Error < StandardError; end
8
9
 
9
10
  class << self
11
+ include NeetoDeploy::Logger
12
+
10
13
  def setup_middleware
11
- require "neetodeploy/autoscale/middleware"
12
14
  Neetodeploy::Autoscale.setup_rails if defined? Rails
13
- Neetodeploy::Autoscale.setup_sidekiq if defined?(Sidekiq)
14
15
  end
15
16
 
16
17
  def setup_rails
17
18
  require "neetodeploy/autoscale/railtie"
18
19
  end
19
20
 
20
- def setup_sidekiq
21
- require "neetodeploy/autoscale/sidekiq_middleware"
21
+ def enable_middleware?
22
+ ENV["NEETODEPLOY_APP_NAME"].present? && ENV["DISABLE_NEETO_DEPLOY_AUTOSCALE"] != "true"
23
+ end
22
24
 
23
- Sidekiq.configure_server do |config|
24
- config.server_middleware do |chain|
25
- chain.add Neetodeploy::SidekiqMiddleware
26
- end
25
+ def initialize_if_enabled
26
+ if enable_middleware?
27
+ logger.info("Setting up Neetodeploy autoscale middleware")
28
+ setup_middleware
29
+ else
30
+ logger.info("Conditions not met skipping NeetoDeploy autoscale middleware setup")
27
31
  end
28
32
  end
29
33
  end
30
34
  end
31
35
  end
32
36
 
33
- ENV.to_h["NEETODEPLOY_APP_NAME"].present? && Neetodeploy::Autoscale.setup_middleware
37
+ Neetodeploy::Autoscale.initialize_if_enabled
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: neetodeploy-autoscale
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.5
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sreeram Venkitesh
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-26 00:00:00.000000000 Z
11
+ date: 2025-03-19 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: For automatically scaling your Rails application based on network metrics
14
14
  email:
@@ -23,11 +23,15 @@ files:
23
23
  - README.md
24
24
  - Rakefile
25
25
  - lib/neetodeploy-autoscale.rb
26
+ - lib/neetodeploy/autoscale/config.rb
27
+ - lib/neetodeploy/autoscale/logger.rb
28
+ - lib/neetodeploy/autoscale/metrics.rb
29
+ - lib/neetodeploy/autoscale/metrics_collector.rb
30
+ - lib/neetodeploy/autoscale/metrics_store.rb
26
31
  - lib/neetodeploy/autoscale/middleware.rb
27
32
  - lib/neetodeploy/autoscale/railtie.rb
28
- - lib/neetodeploy/autoscale/sidekiq_middleware.rb
33
+ - lib/neetodeploy/autoscale/reporter.rb
29
34
  - lib/neetodeploy/autoscale/version.rb
30
- - lib/neetodeploy/autoscale/worker.rb
31
35
  homepage: https://neetodeploy.com
32
36
  licenses: []
33
37
  metadata:
@@ -49,7 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
49
53
  - !ruby/object:Gem::Version
50
54
  version: '0'
51
55
  requirements: []
52
- rubygems_version: 3.4.10
56
+ rubygems_version: 3.4.19
53
57
  signing_key:
54
58
  specification_version: 4
55
59
  summary: neetoDeploy autoscaler gem
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "net/http"
4
- require "time"
5
-
6
- module Neetodeploy
7
- class SidekiqMiddleware
8
- def call(worker, job, queue)
9
- url = URI.parse("http://nd-queue-time-exporter-web-deployment:3000/metrics")
10
-
11
- queues_by_name = ::Sidekiq::Queue.all.each_with_object({}) do |queue_name, obj|
12
- obj[queue_name.name] = queue_name
13
- end
14
-
15
- queues = queues_by_name.keys
16
-
17
- queues.each do |queue_obj|
18
- queue = queues_by_name.fetch(queue_obj) { |name| ::Sidekiq::Queue.new(name) }
19
-
20
- query_params = URI.encode_www_form(app_name: ENV.to_h["NEETODEPLOY_APP_NAME"], process_type: "worker", queue_name: queue_obj, queue_time: queue_time(queue))
21
- url.query = query_params
22
- post = Net::HTTP::Post.new(url)
23
- post["AuthToken"] = "K0An3O3MSyEEMTCnRd1IHgGjdGQkzy"
24
-
25
- begin
26
- Net::HTTP.start(url.host, url.port, use_ssl: false) do |http|
27
- http.request(post)
28
- end
29
- rescue => e
30
- puts "Exception in sending post request to exporter from Sidekiq process: #{e.message}"
31
- end
32
- end
33
-
34
- yield
35
- end
36
-
37
- private
38
-
39
- def queues
40
- ::Sidekiq::Queue.all
41
- end
42
-
43
- def queue_time(queue_obj)
44
- queue = ::Sidekiq::Queue.new(queue_obj.name)
45
-
46
- (queue.latency * 1000).ceil
47
- end
48
- end
49
- end
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "net/http"
4
- require "time"
5
-
6
- module Neetodeploy
7
- class Worker
8
- attr_reader :mutex, :queue, :thread
9
-
10
- def initialize
11
- @mutex = Mutex.new
12
- @queue = Queue.new
13
-
14
- end
15
-
16
- def push(msg)
17
- start && queue.push(msg)
18
- end
19
-
20
- def start
21
- mutex.synchronize do
22
- return true if thread&.alive?
23
-
24
- @thread = Thread.new { run }
25
- end
26
- true
27
- end
28
-
29
- private
30
-
31
- def run
32
- until queue.empty?
33
- queue_time = queue.pop
34
- work(queue_time)
35
- end
36
- end
37
-
38
- def work(payload)
39
- url = URI.parse("http://nd-queue-time-exporter-web-deployment:3000/metrics")
40
- url.query = payload
41
- post = Net::HTTP::Post.new(url)
42
- post["AuthToken"] = "K0An3O3MSyEEMTCnRd1IHgGjdGQkzy"
43
-
44
- begin
45
- Net::HTTP.start(url.host, url.port, use_ssl: false) do |http|
46
- response = http.request(post)
47
- end
48
- rescue StandardError => e
49
- puts "Exception in sending post request to exporter from Rails process: #{e.message}"
50
- end
51
- end
52
- end
53
- end