judoscale-ruby 1.13.3 → 1.13.4

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: 6caa0ab33b7a1b3619b550e988eac92727046cf876aabcd3cb9a6c936c13a44c
4
- data.tar.gz: bb5928ffa44bd5c2becf32672f310ec128eb90d20fed6dee870cae5d37113d4e
3
+ metadata.gz: a9bf6350ab170b86f70461084c4e22bc462826e58c83811ba00068050f6677d8
4
+ data.tar.gz: 0ddbfb24c27cce98a0ce7cc4e24af1a0df7733e92a06010023643dad2b25bed9
5
5
  SHA512:
6
- metadata.gz: 836a7c518ea2c42148c2698a847fe1b3a5c7cb48131f06c0686fa764ce590010a517ae1dc4934cd2017a9d9c4097416487965e068320c15426ffe6dc58d0e9da
7
- data.tar.gz: 7152b6fbf6a409307363516da0635c540d16e2f52b489c8e259ec3a3b60994c70340ae6aa75e6cfa68c678a95ee0a961517506aad768a037f0cefcc4950fa933
6
+ metadata.gz: 14f1dee46bbf8f676500f6c9674aabb876e69e3dd18305eb420813a8ee0b72d86f8e084ac788d9b38879c46a972ccc16a8207057052d7068e748ab0899213d4f
7
+ data.tar.gz: 770ccd3b9725fea1dcff4dfe7d2bbacdda6ec843252dbc42f3e8c30863889550bcc0040781310223ef58d85f757bff84b7d2d14baa9ca7ef70d8c58bb85b0262
@@ -2,21 +2,10 @@
2
2
 
3
3
  require "singleton"
4
4
  require "logger"
5
+ require "judoscale/platform"
5
6
 
6
7
  module Judoscale
7
8
  class Config
8
- class RuntimeContainer < String
9
- # Job metrics are collected from a single container per process type when we
10
- # can identify the ordinal instance (Heroku "web.2", Scalingo "web-2").
11
- # Opaque IDs (Render, ECS, Railway, etc.) are always non-redundant.
12
- ORDINAL_CONTAINER = /\A[a-z_]+[.-](\d{1,3})\z/
13
-
14
- def redundant_instance?
15
- match = match(ORDINAL_CONTAINER)
16
- match ? match[1].to_i > 1 : false
17
- end
18
- end
19
-
20
9
  class JobAdapterConfig
21
10
  UUID_REGEXP = /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/
22
11
  DEFAULT_QUEUE_FILTER = ->(queue_name) { !UUID_REGEXP.match?(queue_name) }
@@ -90,7 +79,7 @@ module Judoscale
90
79
  end
91
80
 
92
81
  attr_accessor :api_base_url, :report_interval_seconds,
93
- :max_request_size_bytes, :logger, :log_tag, :current_runtime_container
82
+ :max_request_size_bytes, :logger, :log_tag, :current_platform
94
83
  attr_reader :log_level
95
84
 
96
85
  def initialize
@@ -108,31 +97,9 @@ module Judoscale
108
97
 
109
98
  self.class.adapter_configs.each(&:reset)
110
99
 
111
- @current_runtime_container =
112
- if ENV.include?("JUDOSCALE_CONTAINER")
113
- RuntimeContainer.new ENV["JUDOSCALE_CONTAINER"]
114
- elsif ENV.include?("DYNO")
115
- RuntimeContainer.new ENV["DYNO"]
116
- elsif ENV.include?("RENDER_INSTANCE_ID")
117
- # Deprecated API url using the service ID for legacy render services not using `JUDOSCALE_URL`.
118
- @api_base_url ||= "https://adapter.judoscale.com/api/#{ENV["RENDER_SERVICE_ID"]}"
119
-
120
- instance = ENV["RENDER_INSTANCE_ID"].delete_prefix("#{ENV["RENDER_SERVICE_ID"]}-")
121
- RuntimeContainer.new instance
122
- elsif ENV.include?("ECS_CONTAINER_METADATA_URI")
123
- instance = ENV["ECS_CONTAINER_METADATA_URI"].split("/").last
124
- RuntimeContainer.new instance
125
- elsif ENV.include?("FLY_MACHINE_ID")
126
- RuntimeContainer.new ENV["FLY_MACHINE_ID"]
127
- elsif ENV.include?("RAILWAY_REPLICA_ID")
128
- RuntimeContainer.new ENV["RAILWAY_REPLICA_ID"]
129
- elsif ENV.include?("CONTAINER")
130
- # Scalingo exposes the container type and index (e.g. "web-1") via CONTAINER.
131
- RuntimeContainer.new ENV["CONTAINER"]
132
- else
133
- # Unsupported platform...
134
- RuntimeContainer.new("")
135
- end
100
+ @current_platform = Platform.detect(ENV)
101
+ # Legacy Render services not using JUDOSCALE_URL derive the API url from the platform.
102
+ @api_base_url ||= @current_platform.default_api_base_url
136
103
  end
137
104
 
138
105
  def log_level=(new_level)
@@ -9,7 +9,7 @@ module Judoscale
9
9
  include Judoscale::Logger
10
10
 
11
11
  def self.collect?(config)
12
- super && !config.current_runtime_container.redundant_instance? && adapter_config.enabled
12
+ super && !config.current_platform.redundant_instance? && adapter_config.enabled
13
13
  end
14
14
 
15
15
  def self.adapter_name
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Judoscale
4
+ # The hosting platform we detected from the environment. The container/instance
5
+ # id is just one property of the platform — behavior that only applies to
6
+ # certain platforms (whether an instance is a redundant member of a formation,
7
+ # or an ephemeral process) lives on the platform subclasses that actually have
8
+ # those concepts, instead of being re-derived from the shape of the container
9
+ # string.
10
+ class Platform
11
+ attr_reader :container
12
+
13
+ def initialize(container)
14
+ @container = container.to_s
15
+ end
16
+
17
+ # Most platforms expose opaque, non-ordinal instance ids (Render, ECS, Fly,
18
+ # Railway, custom), so by default no instance is redundant or ephemeral.
19
+ # Platforms that have those concepts override these.
20
+ def redundant_instance?
21
+ false
22
+ end
23
+
24
+ def ephemeral_instance?
25
+ false
26
+ end
27
+
28
+ # Platforms may contribute a default API base url when one isn't configured.
29
+ def default_api_base_url
30
+ nil
31
+ end
32
+
33
+ # Detect the current platform from the environment. Order matters: an explicit
34
+ # JUDOSCALE_CONTAINER always wins, and Unknown is the fallback.
35
+ def self.detect(env = ENV)
36
+ if env.include?("JUDOSCALE_CONTAINER")
37
+ Custom.new env["JUDOSCALE_CONTAINER"]
38
+ elsif env.include?("DYNO")
39
+ Heroku.new env["DYNO"]
40
+ elsif env.include?("RENDER_INSTANCE_ID")
41
+ Render.new env["RENDER_INSTANCE_ID"], service_id: env["RENDER_SERVICE_ID"]
42
+ elsif env.include?("ECS_CONTAINER_METADATA_URI")
43
+ Ecs.new env["ECS_CONTAINER_METADATA_URI"].split("/").last
44
+ elsif env.include?("FLY_MACHINE_ID")
45
+ Fly.new env["FLY_MACHINE_ID"]
46
+ elsif env.include?("RAILWAY_REPLICA_ID")
47
+ Railway.new env["RAILWAY_REPLICA_ID"]
48
+ elsif env.include?("CONTAINER")
49
+ # Scalingo exposes the container type and index (e.g. "web-1") via CONTAINER.
50
+ Scalingo.new env["CONTAINER"]
51
+ else
52
+ Unknown.new ""
53
+ end
54
+ end
55
+
56
+ # Heroku dynos are named "web.2". We collect job metrics from a single
57
+ # container per process type, so any instance beyond the first is redundant —
58
+ # it would only duplicate the queue metrics the first instance already reports.
59
+ class Heroku < Platform
60
+ def redundant_instance?
61
+ match = container.match(/\A[a-z_]+\.(\d+)\z/)
62
+ match ? match[1].to_i > 1 : false
63
+ end
64
+
65
+ # Heroku release phase and one-off dynos are named "release.1234" and "run.1234".
66
+ def ephemeral_instance?
67
+ normalized_container = container.downcase
68
+ normalized_container.start_with?("release.", "run.")
69
+ end
70
+ end
71
+
72
+ # Scalingo containers are named "web-2", same redundancy rule as Heroku.
73
+ class Scalingo < Platform
74
+ def redundant_instance?
75
+ match = container.match(/\A[a-z_]+-(\d+)\z/)
76
+ match ? match[1].to_i > 1 : false
77
+ end
78
+
79
+ # Scalingo one-off containers are named "one-off-1234".
80
+ def ephemeral_instance?
81
+ container.start_with?("one-off-")
82
+ end
83
+ end
84
+
85
+ class Render < Platform
86
+ def initialize(instance_id, service_id:)
87
+ @service_id = service_id
88
+ # Render prefixes the instance id with the service id, which isn't part of the instance.
89
+ super(instance_id.delete_prefix("#{service_id}-"))
90
+ end
91
+
92
+ # Legacy Render services not using JUDOSCALE_URL derive the adapter URL
93
+ # from the service id.
94
+ def default_api_base_url
95
+ "https://adapter.judoscale.com/api/#{@service_id}"
96
+ end
97
+ end
98
+
99
+ class Ecs < Platform
100
+ end
101
+
102
+ class Fly < Platform
103
+ end
104
+
105
+ class Railway < Platform
106
+ end
107
+
108
+ # User-provided container id via JUDOSCALE_CONTAINER.
109
+ class Custom < Platform
110
+ end
111
+
112
+ # Unsupported or undetected platform.
113
+ class Unknown < Platform
114
+ end
115
+ end
116
+ end
@@ -12,7 +12,7 @@ module Judoscale
12
12
 
13
13
  def as_json
14
14
  {
15
- container: config.current_runtime_container,
15
+ container: config.current_platform.container,
16
16
  pid: Process.pid,
17
17
  config: config.as_json,
18
18
  adapters: adapters.reduce({}) { |hash, adapter| hash.merge!(adapter.as_json) },
@@ -19,6 +19,11 @@ module Judoscale
19
19
  def start!(config, adapters)
20
20
  @pid = Process.pid
21
21
 
22
+ if config.current_platform.ephemeral_instance?
23
+ logger.debug "Reporter not started: in an ephemeral container"
24
+ return
25
+ end
26
+
22
27
  if config.api_base_url.nil? || config.api_base_url.strip.empty?
23
28
  logger.debug "Set api_base_url to enable metrics reporting"
24
29
  return
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Judoscale
4
- VERSION = "1.13.3"
4
+ VERSION = "1.13.4"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: judoscale-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.13.3
4
+ version: 1.13.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam McCrea
@@ -40,6 +40,7 @@ files:
40
40
  - lib/judoscale/metric.rb
41
41
  - lib/judoscale/metrics_collector.rb
42
42
  - lib/judoscale/metrics_store.rb
43
+ - lib/judoscale/platform.rb
43
44
  - lib/judoscale/report.rb
44
45
  - lib/judoscale/reporter.rb
45
46
  - lib/judoscale/request_metrics.rb