neetodeploy-autoscale 1.0.6 → 2.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 50af0e2a9a2c425e9f496a34540b996ba5bad120e6f0f12a4d4f97d778c5141b
4
- data.tar.gz: b2dec0286ccc386f4908a7f409c14cd6c2bb6d9f81025b2164f10eb849c1c620
3
+ metadata.gz: 5e1c015830cddd642f39eaed2ce6c3ddd109c011658ffd3984f7372918b6e2b1
4
+ data.tar.gz: 25dd45ae42aa37ca74de6f8c632c90ddbce8e888c7eddcf707f43d36253ea1eb
5
5
  SHA512:
6
- metadata.gz: 425ba48cac96eef297bb510cf960f904ec760008dca4df78c3bbaad09253f028be24ad17a50fb5729694a3a99504627defdd42a53952dfeaf39b123503a9fab3
7
- data.tar.gz: 0bb779eb933fbd7eaf4edfe854b103d717c880cadecfdf4b00e8e7d3267e2995045a62c69d740c73abfa9db78ac200c9b8bd95dd82f1f5595229dae73fdba26f
6
+ metadata.gz: 1f24c4113d719449a197b1efd48746f7a7a6fd41d5c729749d5b305558361d5ebc489641a49dd5911d29c96d1992a4faf1eaf654c24f8163c8497ad45cf0be39
7
+ data.tar.gz: f85fdb3f24dd9b613a31d22b49ea5058de3edb405761db01c84b84286f43274d422fb80a28984d4633e65c4cbe73d69c94bfc5b3b7ccb6a875598c41b3ab8096
@@ -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,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+
5
+ module Neetodeploy
6
+ class MetricsStore
7
+ include Singleton
8
+
9
+ attr_reader :metrics
10
+
11
+ def initialize
12
+ @metrics = []
13
+ end
14
+
15
+ def push(queue_time)
16
+ @metrics << queue_time
17
+ end
18
+
19
+ def flush
20
+ result = @metrics
21
+ @metrics = []
22
+ result
23
+ end
24
+ end
25
+ end
@@ -2,44 +2,27 @@
2
2
 
3
3
  require "net/http"
4
4
  require "time"
5
- require "neetodeploy/autoscale/exporter"
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
-
10
11
  def initialize(app)
11
12
  @app = app
12
13
  end
13
14
 
14
15
  def call(env)
15
- payload = URI.encode_www_form(
16
- app_name: ENV.to_h["NEETODEPLOY_APP_NAME"],
17
- process_type: "web",
18
- queue_time: queue_time(env)
19
- )
20
- Neetodeploy::Exporter.export(payload)
21
- @app.call(env)
22
- end
16
+ metrics = Metrics.new(env)
17
+ queue_time = metrics.queue_time unless metrics.ignore?
18
+ MetricsCollector.start
23
19
 
24
- private
25
-
26
- def started_at(env)
27
- return nil unless env["HTTP_X_REQUEST_START"].present?
28
-
29
- env["HTTP_X_REQUEST_START"].to_i
30
- end
20
+ if queue_time
21
+ store = MetricsStore.instance
22
+ store.push queue_time
23
+ end
31
24
 
32
- def network_time(env)
33
- env["puma.request_body_wait"].to_i
34
- end
35
-
36
- def queue_time(now = Time.now.to_f * 1000, env)
37
- return if started_at(env).nil?
38
-
39
- queue_time = (now - started_at(env)).round
40
- queue_time -= network_time(env)
41
-
42
- queue_time.positive? ? queue_time : 0
25
+ @app.call(env)
43
26
  end
44
27
  end
45
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.6"
5
+ VERSION = "2.0.1"
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.6
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sreeram Venkitesh
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-02-17 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,10 +23,14 @@ files:
23
23
  - README.md
24
24
  - Rakefile
25
25
  - lib/neetodeploy-autoscale.rb
26
- - lib/neetodeploy/autoscale/exporter.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
27
31
  - lib/neetodeploy/autoscale/middleware.rb
28
32
  - lib/neetodeploy/autoscale/railtie.rb
29
- - lib/neetodeploy/autoscale/sidekiq_middleware.rb
33
+ - lib/neetodeploy/autoscale/reporter.rb
30
34
  - lib/neetodeploy/autoscale/version.rb
31
35
  homepage: https://neetodeploy.com
32
36
  licenses: []
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "net/http"
4
- require "time"
5
-
6
- module Neetodeploy
7
- class Exporter
8
- def self.export(payload)
9
- Thread.new do
10
- url = URI.parse("http://nd-queue-time-exporter-web-deployment:3000/metrics")
11
- url.query = payload
12
- post = Net::HTTP::Post.new(url)
13
- post["AuthToken"] = "K0An3O3MSyEEMTCnRd1IHgGjdGQkzy"
14
-
15
- Net::HTTP.start(url.host, url.port, use_ssl: false) do |http|
16
- http.request(post)
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -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