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 +4 -4
- data/lib/neetodeploy/autoscale/config.rb +19 -0
- data/lib/neetodeploy/autoscale/logger.rb +15 -0
- data/lib/neetodeploy/autoscale/metrics.rb +26 -0
- data/lib/neetodeploy/autoscale/metrics_collector.rb +53 -0
- data/lib/neetodeploy/autoscale/metrics_store.rb +27 -0
- data/lib/neetodeploy/autoscale/middleware.rb +11 -36
- data/lib/neetodeploy/autoscale/railtie.rb +8 -6
- data/lib/neetodeploy/autoscale/reporter.rb +27 -0
- data/lib/neetodeploy/autoscale/version.rb +1 -1
- data/lib/neetodeploy-autoscale.rb +13 -9
- metadata +9 -5
- data/lib/neetodeploy/autoscale/sidekiq_middleware.rb +0 -49
- data/lib/neetodeploy/autoscale/worker.rb +0 -53
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 37bcdd5f87684bd29d215c8ed744b8ff6963a6a57d1af07c5ddadc407f4462d5
|
4
|
+
data.tar.gz: 05fac864a8cca54ac8773e6ec96773d4918961c32fdf1b27ff61049d34241c7f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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/
|
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
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
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.
|
6
|
-
|
6
|
+
initializer "neetodeploy.Neetodeploy.middleware" do |app|
|
7
|
+
app.middleware.insert_before Rack::Runtime, Neetodeploy::Middleware
|
7
8
|
end
|
8
9
|
|
9
|
-
|
10
|
-
|
10
|
+
config.after_initialize do
|
11
|
+
MetricsCollector.start unless in_rails_console_or_runner?
|
11
12
|
end
|
12
13
|
|
13
|
-
def
|
14
|
-
|
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
|
@@ -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
|
21
|
-
|
21
|
+
def enable_middleware?
|
22
|
+
ENV["NEETODEPLOY_APP_NAME"].present? && ENV["DISABLE_NEETO_DEPLOY_AUTOSCALE"] != "true"
|
23
|
+
end
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
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:
|
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:
|
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/
|
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.
|
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
|