neetodeploy-autoscale 2.1.5 → 2.1.6

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: e9c625c0a7ce10076e313d0c9e7055e0c96cb442e3406d1ea9713301d5c3e59b
4
- data.tar.gz: 77ae413d23b920136d168877c6739141de5088b6ecbe3e28f58ea32ed52ca1ff
3
+ metadata.gz: 3b6d47e127492bf2a818f87ad72b9a57c70bb1c4d124b620a495a73d6a814779
4
+ data.tar.gz: 0d211f2a58c654f5e451dcf58c43cac205b0210855afbc4f43ef8db6cb55f66a
5
5
  SHA512:
6
- metadata.gz: 2282c9b7980edb2e17413115c6c06759e3d4a818c0d615f729e04c409f4ea3fb1fd7c1f41e8db1c6fd32a1e27fe30d862eb59e9499666a9dceb604aeb4ece96b
7
- data.tar.gz: ff3d40bc1b40f349b7fc7470cf33f6d7da7daa5c860f556f3f1df4871463956d7341a02c74a58bb5766134f142d64a88bf113c7c1d5bae64094af9242cd00ebb
6
+ metadata.gz: c6647e9faad55fb5e79b2321cb848ebb34d65ec5fae65ee99f0f7cfabf5b018caecb20a38811dd07d4b91ee0acb770ade98c5d46be674fe360d888a5082f989c
7
+ data.tar.gz: 1fd46a9ae0725de810b3109a5011ad7cebea3c851f14168773d8ce3a205cb22f1756ec0f88440badbd84f5f4f3c107b1d5f067494b6446cfda44adbad77f08c9
@@ -2,35 +2,30 @@ module Neetodeploy
2
2
  class Config
3
3
  include Singleton
4
4
 
5
- DEFAULT_METRICS_URL = "http://nd-queue-time-exporter-web-deployment:3000/metrics"
6
- DEFAULT_BATCH_URL = "http://nd-queue-time-exporter-web-deployment:3000/metrics/batch"
7
- DEFAULT_AUTH_TOKEN = "K0An3O3MSyEEMTCnRd1IHgGjdGQkzy"
8
- DEFAULT_REPORT_INTERVAL = 10
9
-
10
- attr_reader :app_name, :metrics_server_url, :metrics_server_batch_url,
11
- :metrics_server_auth_token, :report_interval_seconds
5
+ attr_accessor :disable_auto_scale_gem, :disable_sidekiq_metrics, :app_name, :process_type,
6
+ :metrics_server_url, :metrics_server_auth_token, :report_interval_seconds, :use_puma_queue_size
12
7
 
13
8
  def initialize
9
+ @disable_auto_scale_gem = ENV["DISABLE_NEETO_DEPLOY_AUTOSCALE"]
10
+ @disable_sidekiq_metrics = ENV["DISABLE_NEETO_DEPLOY_SIDEKIQ_METRICS"]
14
11
  @app_name = ENV["NEETODEPLOY_APP_NAME"]
15
- @metrics_server_url = DEFAULT_METRICS_URL
16
- @metrics_server_batch_url = DEFAULT_BATCH_URL
17
- @metrics_server_auth_token = DEFAULT_AUTH_TOKEN
18
- @report_interval_seconds = (ENV["NEETODEPLOY_REPORT_INTERVAL_SECONDS"]&.to_i || DEFAULT_REPORT_INTERVAL)
19
- @gem_disabled = ENV["DISABLE_NEETO_DEPLOY_AUTOSCALE"] == "true"
20
- @sidekiq_metrics_disabled = ENV["DISABLE_NEETO_DEPLOY_SIDEKIQ_METRICS"] == "true"
21
- @batch_processing_enabled = ENV["NEETODEPLOY_BATCH_PROCESSING_ENABLED"] == "true"
12
+ @process_type = ENV["NEETODEPLOY_PROCESS_TYPE"] || "worker"
13
+ @metrics_server_url = "http://nd-queue-time-exporter-web-deployment:3000/metrics"
14
+ @metrics_server_auth_token = "K0An3O3MSyEEMTCnRd1IHgGjdGQkzy"
15
+ @report_interval_seconds = 10
16
+ @use_puma_queue_size = ENV["NEETODEPLOY_USE_PUMA_QUEUE_SIZE"] == "true"
22
17
  end
23
18
 
24
19
  def gem_disabled?
25
- @gem_disabled
20
+ disable_auto_scale_gem == "true"
26
21
  end
27
22
 
28
23
  def sidekiq_metrics_disabled?
29
- @sidekiq_metrics_disabled
24
+ disable_sidekiq_metrics == "true"
30
25
  end
31
26
 
32
- def batch_processing_enabled?
33
- @batch_processing_enabled
27
+ def use_puma_queue_size?
28
+ use_puma_queue_size == true
34
29
  end
35
30
  end
36
31
  end
@@ -1,69 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "neetodeploy/autoscale/config"
4
- require "neetodeploy/autoscale/logger"
5
4
 
6
5
  module Neetodeploy
7
6
  module Rails
8
7
  class Metrics
9
8
  def initialize(env, config = Config.instance)
10
9
  @config = config
11
- @env = env
12
- @request_start_header_raw = env["HTTP_X_REQUEST_START"]
13
- @request_start_header = @request_start_header_raw.to_i
10
+ @request_start_header = env["HTTP_X_REQUEST_START"].to_i
14
11
  @network_time = env["puma.request_body_wait"].to_i
15
-
16
- # Debug: Log all HTTP headers to help diagnose (only for non-health-check requests)
17
- if (@request_start_header_raw.nil? || @request_start_header_raw.empty?) && !health_check_request?(env)
18
- # Get all HTTP_* headers (these are the request headers)
19
- http_headers = env.keys.grep(/^HTTP_/)
20
-
21
- if http_headers.any?
22
- NeetoDeploy::Logger.logger.debug("All HTTP headers found: #{http_headers.sort.join(', ')}")
23
- else
24
- NeetoDeploy::Logger.logger.debug("No HTTP headers found in env. Available env keys: #{env.keys.grep(/^HTTP|^REQUEST|^SERVER/).sort.join(', ')}")
25
- end
26
-
27
- # Also check for X-Request-Start with different casing
28
- x_request_variants = env.select { |k, _| k.to_s.upcase.include?("REQUEST_START") || k.to_s.upcase.include?("X_REQUEST") }
29
- if x_request_variants.any?
30
- NeetoDeploy::Logger.logger.debug("Found X-Request-Start variants: #{x_request_variants.keys.join(', ')}")
31
- end
32
- end
33
12
  end
34
13
 
35
14
  def ignore?
36
- @config.gem_disabled? || health_check_request?(@env)
37
- end
38
-
39
- def health_check_request?(env = @env)
40
- # Ignore health check endpoints that don't go through Traefik
41
- # These typically come from Kubernetes liveness/readiness probes
42
- path = env["PATH_INFO"] || env["REQUEST_PATH"] || ""
43
- path.match?(%r{/health_check|/health|/up|/ready|/live})
15
+ @config.gem_disabled?
44
16
  end
45
17
 
46
18
  def queue_time
47
- # Debug: Log if header is missing or invalid
48
- if @request_start_header_raw.nil? || @request_start_header_raw.empty?
49
- NeetoDeploy::Logger.logger.debug("X-Request-Start header missing or empty")
50
- return nil
51
- end
52
-
53
- return nil if @request_start_header.zero?
19
+ return if @request_start_header.zero?
54
20
 
55
21
  time_now = Time.now.to_f * 1000
56
22
 
57
23
  queue_time = (time_now - @request_start_header).round
58
24
  queue_time -= @network_time
59
25
 
60
- # Log both values for debugging
61
- NeetoDeploy::Logger.logger.debug(
62
- "Queue time calculation: X-Request-Start=#{@request_start_header_raw} (#{@request_start_header}ms), " \
63
- "puma.request_body_wait=#{@network_time}ms, time_now=#{time_now.round}ms, " \
64
- "calculated_queue_time=#{queue_time}ms"
65
- )
66
-
67
26
  queue_time.positive? ? queue_time : nil
68
27
  end
69
28
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "singleton"
4
+ require "json"
4
5
  require "neetodeploy/autoscale/reporter"
5
6
  require "neetodeploy/autoscale/logger"
6
7
  require "neetodeploy/autoscale/config"
@@ -12,17 +13,16 @@ module Neetodeploy
12
13
  include Singleton
13
14
  include NeetoDeploy::Logger
14
15
 
15
- PERCENTILES = { p90: 0.90, p95: 0.95, p99: 0.99 }.freeze
16
- RESTART_DELAY = 5
17
-
18
16
  def self.start
19
17
  instance.start! unless instance.running?
20
18
  end
21
19
 
22
20
  def start!
23
- logger.info("Starting background worker to collect metrics")
21
+ config = Config.instance
22
+ mode = config.use_puma_queue_size? ? "Puma queue size" : "response time"
23
+ logger.info("Starting background worker to collect #{mode} metrics")
24
24
  @pid = Process.pid
25
- start_collector_thread
25
+ start_thread_with_collector_loop
26
26
  end
27
27
 
28
28
  def stop!
@@ -32,28 +32,30 @@ module Neetodeploy
32
32
  end
33
33
 
34
34
  def running?
35
- @pid == Process.pid && @thread&.alive?
35
+ @pid == Process.pid and @thread&.alive?
36
36
  end
37
37
 
38
- private
39
-
40
- def start_collector_thread(config = Config.instance)
38
+ def start_thread_with_collector_loop(config = Config.instance)
41
39
  @thread = Thread.new do
40
+ metrics_store = MetricsStore.instance unless config.use_puma_queue_size?
42
41
  loop do
43
- begin
44
- metrics_store = MetricsStore.instance
45
- collect_and_report_loop(metrics_store, config)
46
- rescue StandardError => e
47
- handle_collection_error(e, config)
42
+ if config.use_puma_queue_size?
43
+ run_puma_metrics_collection(config)
44
+ else
45
+ run_queue_time_collection(metrics_store, config)
48
46
  end
47
+ multiplier = 1 - (rand / 4)
48
+ sleep config.report_interval_seconds * multiplier
49
+ end
50
+ rescue StandardError => e
51
+ logger.error("Rails metrics collector thread terminated with error: #{e.message}")
52
+ logger.error(e.backtrace.join("\n")) if e.backtrace
53
+ ensure
54
+ if @pid == Process.pid && !@thread.nil?
55
+ logger.info("Restarting Rails metrics collector thread")
56
+ sleep(5)
57
+ start_thread_with_collector_loop
49
58
  end
50
- end
51
- end
52
-
53
- def collect_and_report_loop(metrics_store, config)
54
- loop do
55
- run_queue_time_collection(metrics_store, config)
56
- sleep(config.report_interval_seconds)
57
59
  end
58
60
  end
59
61
 
@@ -63,71 +65,49 @@ module Neetodeploy
63
65
  data = metrics_store.flush
64
66
  return if data.empty?
65
67
 
66
- if config.batch_processing_enabled?
67
- report_batch_metrics(data, config)
68
- else
69
- report_average_metric(data, config)
70
- end
68
+ average_queue_time = data.sum / data.size
69
+ Reporter.new(average_queue_time, "queue_time", "web").report
71
70
  end
72
71
 
73
- def report_batch_metrics(data, config)
74
- sorted_data = data.sort
75
- size = sorted_data.size
76
-
77
- # Calculate percentiles
78
- p90 = percentile_value(sorted_data, size, 0.90)
79
- p95 = percentile_value(sorted_data, size, 0.95)
80
- p99 = percentile_value(sorted_data, size, 0.99)
81
- avg = data.sum / data.size
82
-
83
- min_val = sorted_data.first
84
- max_val = sorted_data.last
85
- logger.debug("Calculated metrics from #{size} samples: min=#{min_val}, max=#{max_val}, avg=#{avg}, p90=#{p90}, p95=#{p95}, p99=#{p99}")
72
+ def run_puma_metrics_collection(config)
73
+ return unless puma_available?
74
+ return if config.gem_disabled?
86
75
 
87
- if size <= 10
88
- logger.debug("All values in this batch: #{sorted_data.join(', ')}")
89
- elsif size <= 50
90
- logger.debug("Sample values: first 5=#{sorted_data[0..4].join(', ')}, last 5=#{sorted_data[-5..-1].join(', ')}")
91
- end
76
+ begin
77
+ queue_size = get_puma_queue_size
78
+ return if queue_size.nil?
92
79
 
93
- if max_val > 500 && p95 < max_val * 0.5
94
- logger.warn("High queue time detected (#{max_val}ms) but P95 is much lower (#{p95}ms). Sample size: #{size}. This suggests high values are being diluted by many low values.")
80
+ Reporter.new(queue_size, "puma_queue_size", "web").report
81
+ rescue StandardError => e
82
+ logger.error("Error collecting Puma queue size metrics: #{e.message}")
95
83
  end
96
-
97
- metrics = [
98
- { metric_name: "queue_time_p90", metric_value: p90 },
99
- { metric_name: "queue_time_p95", metric_value: p95 },
100
- { metric_name: "queue_time_p99", metric_value: p99 },
101
- { metric_name: "queue_time_max", metric_value: max_val },
102
- { metric_name: "queue_time", metric_value: avg }
103
- ]
104
-
105
- Reporter.report_batch(metrics, "web", nil, config)
106
84
  end
107
85
 
108
- def report_average_metric(data, config)
109
- average_queue_time = data.sum.fdiv(data.size).round
110
- Reporter.new(average_queue_time, "queue_time", "web", nil, config).report
111
- end
86
+ private
112
87
 
113
- def percentile_value(sorted_data, size, percentile)
114
- # Use nearest rank method: index = ceil(size * percentile) - 1
115
- # For small samples, this ensures we get a valid index
116
- index = [(size * percentile).ceil - 1, 0].max
117
- # Clamp index to valid range
118
- index = [index, size - 1].min
119
- sorted_data[index]
120
- end
88
+ def puma_available?
89
+ defined?(::Puma) && ::Puma.respond_to?(:stats)
90
+ end
121
91
 
122
- def handle_collection_error(error, config)
123
- logger.error("Rails metrics collector thread error: #{error.message}")
124
- logger.error(error.backtrace.join("\n")) if error.backtrace
92
+ def get_puma_queue_size
93
+ stats_json = ::Puma.stats
94
+ return nil if stats_json.nil? || stats_json.empty?
125
95
 
126
- should_restart = @pid == Process.pid && @thread
127
- logger.info("Restarting Rails metrics collector thread") if should_restart
96
+ stats = JSON.parse(stats_json)
128
97
 
129
- sleep(RESTART_DELAY) if should_restart
130
- end
98
+ # For clustered mode (multiple workers)
99
+ if stats["worker_status"]
100
+ # Sum backlog from all workers
101
+ stats["worker_status"].sum do |worker|
102
+ worker.dig("last_status", "backlog") || 0
103
+ end
104
+ # For single mode
105
+ elsif stats["backlog"]
106
+ stats["backlog"]
107
+ else
108
+ nil
109
+ end
110
+ end
131
111
  end
132
112
  end
133
113
  end
@@ -1,71 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "singleton"
4
- require "neetodeploy/autoscale/logger"
5
4
 
6
5
  module Neetodeploy
7
6
  module Rails
8
7
  class MetricsStore
9
8
  include Singleton
10
9
 
11
- # Use sliding window: keep last 2 minutes of data (120 seconds)
12
- # At 2s reporting interval, this gives us ~60 samples per window
13
- # This ensures percentiles work correctly even with low traffic
14
- WINDOW_SIZE_SECONDS = 120
15
- MAX_SAMPLES = 1000 # Safety limit to prevent memory bloat
16
-
17
10
  attr_reader :metrics
18
11
 
19
12
  def initialize
20
- @metrics = [] # Array of {time: timestamp, value: queue_time}
21
- @mutex = Mutex.new
13
+ @metrics = []
22
14
  end
23
15
 
24
16
  def push(queue_time)
25
- @mutex.synchronize do
26
- now = Time.now.to_f
27
- @metrics << { time: now, value: queue_time }
28
-
29
- # Remove old data outside window
30
- cutoff = now - WINDOW_SIZE_SECONDS
31
- @metrics.reject! { |m| m[:time] < cutoff }
32
-
33
- # Safety limit: keep only most recent samples
34
- if @metrics.size > MAX_SAMPLES
35
- @metrics = @metrics.last(MAX_SAMPLES)
36
- end
37
-
38
- # Log high queue times to help debug
39
- if queue_time > 500
40
- NeetoDeploy::Logger.logger.debug("MetricsStore: Pushed high queue_time=#{queue_time}ms (total in window: #{@metrics.size})")
41
- end
42
- end
43
- end
44
-
45
- # Get recent samples from the sliding window (last N seconds)
46
- def get_recent(seconds = WINDOW_SIZE_SECONDS)
47
- @mutex.synchronize do
48
- cutoff = Time.now.to_f - seconds
49
- @metrics.select { |m| m[:time] >= cutoff }.map { |m| m[:value] }
50
- end
17
+ @metrics << queue_time
51
18
  end
52
19
 
53
- # Flush: return all values in window, but DON'T clear (sliding window)
54
- # This allows high values to persist across multiple reporting cycles
55
20
  def flush
56
- @mutex.synchronize do
57
- # Clean up old data first
58
- cutoff = Time.now.to_f - WINDOW_SIZE_SECONDS
59
- @metrics.reject! { |m| m[:time] < cutoff }
60
-
61
- result = @metrics.map { |m| m[:value] }
62
- size = result.size
63
- max_val = result.max if result.any?
64
- NeetoDeploy::Logger.logger.debug("MetricsStore: Reporting from sliding window: #{size} values, max=#{max_val}ms") if result.any?
65
-
66
- # Return values but keep them in the window for next cycle
67
- result
68
- end
21
+ result = @metrics
22
+ @metrics = []
23
+ result
69
24
  end
70
25
  end
71
26
  end
@@ -3,6 +3,7 @@
3
3
  require "neetodeploy/autoscale/rails/metrics"
4
4
  require "neetodeploy/autoscale/rails/metrics_store"
5
5
  require "neetodeploy/autoscale/rails/metrics_collector"
6
+ require "neetodeploy/autoscale/config"
6
7
 
7
8
  module Neetodeploy
8
9
  module Rails
@@ -12,13 +13,18 @@ module Neetodeploy
12
13
  end
13
14
 
14
15
  def call(env)
15
- metrics = Metrics.new(env)
16
- queue_time = metrics.queue_time unless metrics.ignore?
16
+ config = Config.instance
17
17
  MetricsCollector.start
18
18
 
19
- if queue_time
20
- store = MetricsStore.instance
21
- store.push queue_time
19
+ # Only collect queue time per request if using the old response time method
20
+ unless config.use_puma_queue_size?
21
+ metrics = Metrics.new(env)
22
+ queue_time = metrics.queue_time unless metrics.ignore?
23
+
24
+ if queue_time
25
+ store = MetricsStore.instance
26
+ store.push queue_time
27
+ end
22
28
  end
23
29
 
24
30
  @app.call(env)
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "net/http"
4
4
  require "time"
5
- require "json"
6
5
  require "neetodeploy/autoscale/config"
7
6
  require "neetodeploy/autoscale/logger"
8
7
 
@@ -19,100 +18,39 @@ module Neetodeploy
19
18
  end
20
19
 
21
20
  def report
22
- url = build_url(common_params)
23
- logger.info("Reporting #{@metric_name} for #{@process_type}: #{@metric_value}")
24
- post_request(url)
25
- end
26
-
27
- def self.report_batch(metrics, process_type, queue_name = nil, config = Config.instance)
28
- return if metrics.empty?
21
+ params = common_params
22
+ params[:queue_name] = @queue_name if @queue_name
23
+ url = build_url(params)
24
+ logger.info("Reporting #{@metric_name} for #{@process_type} dyno: #{@metric_value}")
29
25
 
30
- payload = build_batch_payload(metrics, process_type, queue_name, config)
31
- url = config.metrics_server_batch_url
32
-
33
- NeetoDeploy::Logger.logger.info("Batch reporting #{metrics.size} metrics for #{process_type}")
34
- post_batch_request(url, payload, config)
26
+ post_request(url)
35
27
  end
36
28
 
37
29
  private
38
30
 
39
- def common_params
40
- {
41
- app_name: @config.app_name,
42
- queue_time: @metric_value,
43
- metric_name: @metric_name,
44
- process_type: @process_type,
45
- queue_name: @queue_name
46
- }.compact
47
- end
48
-
49
- def build_url(params)
50
- url = URI.parse(@config.metrics_server_url)
51
- url.query = URI.encode_www_form(params)
52
- url
53
- end
54
-
55
- def post_request(url)
56
- http_request(url, self.class.build_post_request(url, @config.metrics_server_auth_token))
57
- end
58
-
59
- def self.build_batch_payload(metrics, process_type, queue_name, config)
60
- {
61
- app_name: config.app_name,
62
- process_type: process_type,
63
- queue_name: queue_name,
64
- metrics: metrics.map { |m| { metric_name: m[:metric_name], metric_value: m[:metric_value].to_i } }
65
- }.compact
66
- end
67
-
68
- def self.post_batch_request(url, payload, config)
69
- uri = URI.parse(url)
70
- request = build_post_request(uri, config.metrics_server_auth_token)
71
- request.body = JSON.generate(payload)
72
- request["Content-Type"] = "application/json"
73
-
74
- http_request(uri, request)
75
- rescue StandardError => e
76
- NeetoDeploy::Logger.logger.error("Error batch reporting metrics: #{e.message}")
77
- raise
78
- end
79
-
80
- def self.build_post_request(uri, auth_token)
81
- post = Net::HTTP::Post.new(uri)
82
- post["AuthToken"] = auth_token
83
- post
84
- end
31
+ def common_params
32
+ {
33
+ app_name: @config.app_name,
34
+ queue_time: @metric_value, # Keep queue_time parameter name for backward compatibility with metrics server
35
+ metric_name: @metric_name,
36
+ process_type: @process_type
37
+ }
38
+ end
85
39
 
86
- def self.encode_form_data(payload)
87
- form_parts = [
88
- "app_name=#{encode_param(payload[:app_name])}",
89
- "process_type=#{encode_param(payload[:process_type])}"
90
- ]
91
-
92
- form_parts << "queue_name=#{encode_param(payload[:queue_name])}" if payload[:queue_name]
93
-
94
- if payload[:metrics].is_a?(Array)
95
- payload[:metrics].each do |metric|
96
- form_parts << "metrics[][metric_name]=#{encode_param(metric[:metric_name])}"
97
- form_parts << "metrics[][metric_value]=#{encode_param(metric[:metric_value])}"
98
- end
99
- end
100
-
101
- form_parts.join("&")
102
- end
40
+ def post_request(url)
41
+ post = Net::HTTP::Post.new(url)
42
+ post["AuthToken"] = @config.metrics_server_auth_token
103
43
 
104
- def self.encode_param(value)
105
- URI.encode_www_form_component(value.to_s)
44
+ Net::HTTP.start(url.host, url.port, use_ssl: false) do |http|
45
+ http.request(post)
106
46
  end
47
+ end
107
48
 
108
- def self.http_request(uri, request)
109
- Net::HTTP.start(uri.host, uri.port, use_ssl: false) do |http|
110
- http.request(request)
111
- end
112
- end
49
+ def build_url(params = {})
50
+ exporter_url = URI.parse(@config.metrics_server_url)
51
+ exporter_url.query = URI.encode_www_form(params)
113
52
 
114
- def http_request(uri, request)
115
- self.class.http_request(uri, request)
116
- end
53
+ exporter_url
54
+ end
117
55
  end
118
56
  end
@@ -81,7 +81,7 @@ module Neetodeploy
81
81
  end
82
82
 
83
83
  def report_sidekiq_metric(queue_name, queue_time)
84
- Reporter.new(queue_time, "sidekiq_queue_time", "worker", queue_name).report
84
+ Reporter.new(queue_time, "sidekiq_queue_time", Config.instance.process_type, queue_name).report
85
85
  end
86
86
  end
87
87
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Neetodeploy
4
4
  module Autoscale
5
- VERSION = "2.1.5"
5
+ VERSION = "2.1.6"
6
6
  end
7
7
  end
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: 2.1.5
4
+ version: 2.1.6
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-11-27 00:00:00.000000000 Z
11
+ date: 2026-02-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: For automatically scaling your Rails application based on network metrics
14
14
  email:
@@ -56,7 +56,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
56
56
  - !ruby/object:Gem::Version
57
57
  version: '0'
58
58
  requirements: []
59
- rubygems_version: 3.5.16
59
+ rubygems_version: 3.5.22
60
60
  signing_key:
61
61
  specification_version: 4
62
62
  summary: neetoDeploy autoscaler gem