rage-rb 1.26.1 → 1.28.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/CHANGELOG.md +30 -1
- data/CONTRIBUTING.md +10 -1
- data/README.md +1 -1
- data/exe/rage +1 -1
- data/lib/rage/cable/channel.rb +7 -11
- data/lib/rage/cli/base.rb +27 -0
- data/lib/rage/cli/code_generator.rb +58 -0
- data/lib/rage/cli/new_app_generator.rb +66 -0
- data/lib/rage/cli/openapi.rb +20 -0
- data/lib/rage/cli/skills.rb +222 -220
- data/lib/rage/cli.rb +12 -143
- data/lib/rage/code_loader.rb +5 -1
- data/lib/rage/configuration.rb +73 -2
- data/lib/rage/cookies.rb +11 -9
- data/lib/rage/daemon.rb +5 -2
- data/lib/rage/deferred/backends/disk.rb +544 -199
- data/lib/rage/deferred/backends/nil.rb +16 -2
- data/lib/rage/deferred/deferred.rb +5 -1
- data/lib/rage/deferred/queue.rb +8 -3
- data/lib/rage/deferred/task.rb +1 -0
- data/lib/rage/fiber_scheduler.rb +17 -13
- data/lib/rage/hooks.rb +6 -1
- data/lib/rage/logger/logger.rb +55 -4
- data/lib/rage/openapi/index.html.erb +1 -1
- data/lib/rage/openapi/openapi.rb +14 -5
- data/lib/rage/openapi/parsers/ext/blueprinter.rb +20 -12
- data/lib/rage/router/README.md +1 -1
- data/lib/rage/router/backend.rb +8 -4
- data/lib/rage/router/dsl.rb +5 -7
- data/lib/rage/tasks.rb +5 -0
- data/lib/rage/telemetry/capacity.rb +42 -0
- data/lib/rage/telemetry/telemetry.rb +19 -0
- data/lib/rage/version.rb +1 -1
- data/lib/rage-rb.rb +3 -0
- metadata +8 -3
|
@@ -4,13 +4,27 @@ class Rage::Deferred::Backends::Nil
|
|
|
4
4
|
def initialize(**)
|
|
5
5
|
end
|
|
6
6
|
|
|
7
|
-
def
|
|
7
|
+
def add_task(_, **)
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def
|
|
10
|
+
def remove_task(_)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def pending_tasks
|
|
14
14
|
[]
|
|
15
15
|
end
|
|
16
|
+
|
|
17
|
+
def add_dead_task(_, _, _, **)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def list_dead_tasks(**)
|
|
21
|
+
[]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def find_dead_task(_)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def remove_dead_tasks(_)
|
|
28
|
+
0
|
|
29
|
+
end
|
|
16
30
|
end
|
|
@@ -70,7 +70,7 @@ module Rage::Deferred
|
|
|
70
70
|
__queue.schedule(task_id, task_wrapper, publish_in:)
|
|
71
71
|
rescue => e
|
|
72
72
|
puts "ERROR: Failed to load deferred task #{task_id}: #{e.class} (#{e.message}). Removing task from the queue."
|
|
73
|
-
__backend.
|
|
73
|
+
__backend.remove_task(task_id)
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
|
|
@@ -99,6 +99,10 @@ module Rage::Deferred
|
|
|
99
99
|
|
|
100
100
|
class PushTimeout < StandardError
|
|
101
101
|
end
|
|
102
|
+
|
|
103
|
+
# Raised when a dead-tasks store operation cannot acquire the lock within the retry budget.
|
|
104
|
+
class DeadTasksLockTimeout < StandardError
|
|
105
|
+
end
|
|
102
106
|
end
|
|
103
107
|
|
|
104
108
|
require_relative "task"
|
data/lib/rage/deferred/queue.rb
CHANGED
|
@@ -21,7 +21,7 @@ class Rage::Deferred::Queue
|
|
|
21
21
|
[delay_until_i - current_time_i, delay_until_i] if delay_until_i > current_time_i
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
persisted_task_id = @backend.
|
|
24
|
+
persisted_task_id = @backend.add_task(context, publish_at:, task_id:)
|
|
25
25
|
schedule(persisted_task_id, context, publish_in:)
|
|
26
26
|
end
|
|
27
27
|
|
|
@@ -41,14 +41,14 @@ class Rage::Deferred::Queue
|
|
|
41
41
|
result = task.new.__perform(context)
|
|
42
42
|
|
|
43
43
|
if result == true
|
|
44
|
-
@backend.
|
|
44
|
+
@backend.remove_task(task_id)
|
|
45
45
|
else
|
|
46
46
|
attempts = Rage::Deferred::Context.inc_attempts(context)
|
|
47
47
|
retry_in = task.__next_retry_in(attempts, result)
|
|
48
48
|
if retry_in
|
|
49
49
|
enqueue(context, delay: retry_in, task_id:)
|
|
50
50
|
else
|
|
51
|
-
|
|
51
|
+
abandon_task(task_id, context, result, task_class: task, attempts:)
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
54
|
|
|
@@ -74,4 +74,9 @@ class Rage::Deferred::Queue
|
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
end
|
|
77
|
+
|
|
78
|
+
def abandon_task(task_id, context, result, task_class:, attempts:)
|
|
79
|
+
@backend.add_dead_task(task_id, context, result, task_class:, attempts:)
|
|
80
|
+
@backend.remove_task(task_id)
|
|
81
|
+
end
|
|
77
82
|
end
|
data/lib/rage/deferred/task.rb
CHANGED
|
@@ -73,6 +73,7 @@ module Rage::Deferred::Task
|
|
|
73
73
|
Rage.logger.with_context(task_log_context) do
|
|
74
74
|
args = Rage::Deferred::Context.get_args(context)
|
|
75
75
|
kwargs = Rage::Deferred::Context.get_kwargs(context)
|
|
76
|
+
kwargs ||= {} if RUBY_VERSION.start_with?("3.3.")
|
|
76
77
|
|
|
77
78
|
perform(*args, **kwargs)
|
|
78
79
|
end
|
data/lib/rage/fiber_scheduler.rb
CHANGED
|
@@ -75,19 +75,23 @@ class Rage::FiberScheduler
|
|
|
75
75
|
Fiber.pause if duration.nil? || duration < 1
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
78
|
+
if ENV["RAGE_ENABLE_NON_BLOCKING_TIMEOUT"]
|
|
79
|
+
def timeout_after(duration, exception_class = Timeout::Error, *exception_arguments, &block)
|
|
80
|
+
f, fulfilled = Fiber.current, false
|
|
81
|
+
|
|
82
|
+
::Iodine.run_after((duration * 1000).to_i) do
|
|
83
|
+
if !fulfilled && f.alive?
|
|
84
|
+
fulfilled = true
|
|
85
|
+
f.__wait_generation += 1
|
|
86
|
+
f.raise(exception_class, *exception_arguments)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
block.call
|
|
91
|
+
ensure
|
|
92
|
+
fulfilled = true
|
|
93
|
+
end
|
|
94
|
+
end
|
|
91
95
|
|
|
92
96
|
# Resolve a hostname to IP addresses, caching results for 60 seconds.
|
|
93
97
|
def address_resolve(hostname)
|
data/lib/rage/hooks.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Hooks
|
|
|
10
10
|
hooks[hook_family] << callback if callback.is_a?(Proc)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def run_hooks_for
|
|
13
|
+
def run_hooks_for(hook_family, context = nil)
|
|
14
14
|
hooks[hook_family].each do |callback|
|
|
15
15
|
if context
|
|
16
16
|
context.instance_exec(&callback)
|
|
@@ -19,6 +19,11 @@ module Hooks
|
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def run_hooks_for!(hook_family, context = nil)
|
|
26
|
+
run_hooks_for(hook_family, context)
|
|
22
27
|
@hooks[hook_family] = []
|
|
23
28
|
|
|
24
29
|
true
|
data/lib/rage/logger/logger.rb
CHANGED
|
@@ -60,11 +60,13 @@ class Rage::Logger
|
|
|
60
60
|
fatal: Logger::FATAL,
|
|
61
61
|
unknown: Logger::UNKNOWN
|
|
62
62
|
}
|
|
63
|
+
REDACTED = "[REDACTED]"
|
|
64
|
+
private_constant :REDACTED
|
|
63
65
|
|
|
64
66
|
attr_reader :level, :formatter
|
|
65
67
|
|
|
66
68
|
# @private
|
|
67
|
-
attr_reader :dynamic_tags, :dynamic_context
|
|
69
|
+
attr_reader :dynamic_tags, :dynamic_context, :log_redact_keys
|
|
68
70
|
|
|
69
71
|
# @private
|
|
70
72
|
attr_reader :external_logger
|
|
@@ -132,6 +134,19 @@ class Rage::Logger
|
|
|
132
134
|
rebuild!
|
|
133
135
|
end
|
|
134
136
|
|
|
137
|
+
# @private
|
|
138
|
+
def log_redact_keys=(log_redact_keys)
|
|
139
|
+
if log_redact_keys&.any?
|
|
140
|
+
@log_redact_keys = log_redact_keys
|
|
141
|
+
@log_redact_keys_matcher = Regexp.union(log_redact_keys.map { |key| Regexp.new(Regexp.escape(key), Regexp::IGNORECASE) })
|
|
142
|
+
else
|
|
143
|
+
@log_redact_keys = nil
|
|
144
|
+
@log_redact_keys_matcher = nil
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
rebuild!
|
|
148
|
+
end
|
|
149
|
+
|
|
135
150
|
# Add custom keys to an entry.
|
|
136
151
|
#
|
|
137
152
|
# @param context [Hash] a hash of custom keys
|
|
@@ -196,7 +211,7 @@ class Rage::Logger
|
|
|
196
211
|
RUBY
|
|
197
212
|
elsif @external_logger.is_a?(External::Static)
|
|
198
213
|
# an object that implements Ruby's Logger interface is used as a logger
|
|
199
|
-
write_call = <<~RUBY
|
|
214
|
+
write_call = build_redacted_context_call(<<~RUBY)
|
|
200
215
|
@external_logger.wrapped.#{level_name}(
|
|
201
216
|
#{build_formatter_call(level_name, level_val)}
|
|
202
217
|
)
|
|
@@ -233,7 +248,7 @@ class Rage::Logger
|
|
|
233
248
|
request_info: "Fiber[:__rage_logger_final].freeze"
|
|
234
249
|
})
|
|
235
250
|
|
|
236
|
-
write_call = <<~RUBY
|
|
251
|
+
write_call = build_redacted_context_call(<<~RUBY)
|
|
237
252
|
@external_logger.wrapped.call(#{parameters})
|
|
238
253
|
RUBY
|
|
239
254
|
|
|
@@ -253,7 +268,7 @@ class Rage::Logger
|
|
|
253
268
|
end
|
|
254
269
|
RUBY
|
|
255
270
|
else
|
|
256
|
-
write_call = <<~RUBY
|
|
271
|
+
write_call = build_redacted_context_call(<<~RUBY)
|
|
257
272
|
@logdev.write(
|
|
258
273
|
#{build_formatter_call(level_name, level_val)}
|
|
259
274
|
)
|
|
@@ -296,6 +311,42 @@ class Rage::Logger
|
|
|
296
311
|
end
|
|
297
312
|
end
|
|
298
313
|
|
|
314
|
+
def build_redacted_context_call(write_call)
|
|
315
|
+
return write_call unless @log_redact_keys
|
|
316
|
+
|
|
317
|
+
<<~RUBY
|
|
318
|
+
with_redacted_context do
|
|
319
|
+
#{write_call}
|
|
320
|
+
end
|
|
321
|
+
RUBY
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def with_redacted_context
|
|
325
|
+
context = Fiber[:__rage_logger_context]
|
|
326
|
+
return yield if context.nil? || context.empty?
|
|
327
|
+
|
|
328
|
+
Fiber[:__rage_logger_context] = redact_value(context)
|
|
329
|
+
yield
|
|
330
|
+
ensure
|
|
331
|
+
Fiber[:__rage_logger_context] = context
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def redact_value(value)
|
|
335
|
+
if value.is_a?(Hash)
|
|
336
|
+
value.each_with_object({}) do |(key, nested_value), redacted|
|
|
337
|
+
redacted[key] = if @log_redact_keys_matcher.match?(key)
|
|
338
|
+
REDACTED
|
|
339
|
+
else
|
|
340
|
+
redact_value(nested_value)
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
elsif value.is_a?(Array)
|
|
344
|
+
value.map { |item| redact_value(item) }
|
|
345
|
+
else
|
|
346
|
+
value
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
|
|
299
350
|
def with_dynamic_tags_and_context
|
|
300
351
|
do_calls, end_calls = [], []
|
|
301
352
|
|
data/lib/rage/openapi/openapi.rb
CHANGED
|
@@ -42,11 +42,9 @@ module Rage::OpenAPI
|
|
|
42
42
|
# run Rage.openapi.application(namespace: "Api::V2")
|
|
43
43
|
# end
|
|
44
44
|
def self.application(namespace: nil)
|
|
45
|
-
html_app = ->(
|
|
45
|
+
html_app = ->(_) do
|
|
46
46
|
__data_cache[[:page, namespace]] ||= begin
|
|
47
|
-
|
|
48
|
-
spec_url = "#{scheme}://#{host}#{path}/json"
|
|
49
|
-
page = ERB.new(File.read("#{__dir__}/index.html.erb")).result(binding)
|
|
47
|
+
page = ERB.new(File.read("#{__dir__}/index.html.erb")).result
|
|
50
48
|
|
|
51
49
|
[200, { "content-type" => "text/html; charset=UTF-8" }, [page]]
|
|
52
50
|
end
|
|
@@ -120,7 +118,7 @@ module Rage::OpenAPI
|
|
|
120
118
|
|
|
121
119
|
# @private
|
|
122
120
|
def self.__try_parse_collection(str)
|
|
123
|
-
if str =~ /^Array<([\w\s:\(\),]+)>$/ || str =~ /^\[([\w\s:\(\),]+)\]$/
|
|
121
|
+
if str =~ /^Array<([\w\s:\(\),'\"]+)>$/ || str =~ /^\[([\w\s:\(\),'\"]+)\]$/
|
|
124
122
|
[true, $1]
|
|
125
123
|
else
|
|
126
124
|
[false, str]
|
|
@@ -202,9 +200,20 @@ module Rage::OpenAPI
|
|
|
202
200
|
|
|
203
201
|
# @private
|
|
204
202
|
def self.__log_warn(log)
|
|
203
|
+
@__warnings << log if @__warnings
|
|
205
204
|
puts "[OpenAPI] WARNING: #{log}"
|
|
206
205
|
end
|
|
207
206
|
|
|
207
|
+
# @private
|
|
208
|
+
# @return [Array<String>] the warnings logged inside the block
|
|
209
|
+
def self.__collect_warnings
|
|
210
|
+
@__warnings = []
|
|
211
|
+
yield
|
|
212
|
+
@__warnings
|
|
213
|
+
ensure
|
|
214
|
+
@__warnings = nil
|
|
215
|
+
end
|
|
216
|
+
|
|
208
217
|
module Nodes
|
|
209
218
|
end
|
|
210
219
|
|
|
@@ -20,9 +20,10 @@ class Rage::OpenAPI::Parsers::Ext::Blueprinter
|
|
|
20
20
|
is_collection, raw_klass_str, serializer_options = Rage::OpenAPI.__parse_serializer_args(klass_str)
|
|
21
21
|
klass = @namespace.const_get(raw_klass_str)
|
|
22
22
|
schema = build_schema(klass, is_collection, serializer_options)
|
|
23
|
+
registry_key = "#{raw_klass_str}_#{serializer_options[:view] || :default}"
|
|
23
24
|
|
|
24
|
-
if @root.schema_registry.key?(
|
|
25
|
-
@root.schema_registry[
|
|
25
|
+
if @root.schema_registry.key?(registry_key)
|
|
26
|
+
@root.schema_registry[registry_key] = is_collection ? schema["items"] : schema
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
schema
|
|
@@ -32,26 +33,31 @@ class Rage::OpenAPI::Parsers::Ext::Blueprinter
|
|
|
32
33
|
|
|
33
34
|
private
|
|
34
35
|
|
|
35
|
-
def build_schema(klass, is_collection, serializer_options
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
view_name = serializer_options&.key?(:view) ? serializer_options[:view] : :default
|
|
36
|
+
def build_schema(klass, is_collection, serializer_options)
|
|
37
|
+
view_name = serializer_options&.key?(:view) ? serializer_options[:view].to_sym : :default
|
|
39
38
|
reflections = klass.reflections
|
|
40
39
|
view = reflections[view_name]
|
|
41
40
|
raise InvalidViewError, "invalid view #{view_name}" unless view
|
|
42
41
|
|
|
42
|
+
@parsing_stack.add([klass.name, view_name])
|
|
43
|
+
|
|
43
44
|
identifier_fields = extract_fields(reflections[:identifier])
|
|
44
45
|
default_fields = extract_fields(view)
|
|
45
46
|
association_fields = extract_associations(view)
|
|
46
47
|
|
|
47
|
-
@parsing_stack.delete(klass.name)
|
|
48
|
+
@parsing_stack.delete([klass.name, view_name])
|
|
48
49
|
|
|
49
50
|
schema = identifier_fields.merge(default_fields.merge(association_fields).sort.to_h)
|
|
50
51
|
|
|
51
52
|
result = { "type" => "object" }
|
|
52
53
|
result["properties"] = schema if schema.any?
|
|
53
54
|
result = { "type" => "array", "items" => result } if is_collection
|
|
54
|
-
|
|
55
|
+
|
|
56
|
+
if serializer_options&.key?(:root)
|
|
57
|
+
{ "type" => "object", "properties" => { serializer_options[:root].to_s => result } }
|
|
58
|
+
else
|
|
59
|
+
result
|
|
60
|
+
end
|
|
55
61
|
end
|
|
56
62
|
|
|
57
63
|
def extract_fields(view)
|
|
@@ -65,14 +71,16 @@ class Rage::OpenAPI::Parsers::Ext::Blueprinter
|
|
|
65
71
|
blueprint = association.blueprint
|
|
66
72
|
name, display_name = association.name.to_s, association.display_name.to_s
|
|
67
73
|
is_collection = collection_association?(name)
|
|
74
|
+
serializer_options = { view: association.view }
|
|
68
75
|
|
|
69
76
|
item_schema = if blueprint.is_a?(Proc)
|
|
70
77
|
{ "type" => "object" }
|
|
71
|
-
elsif @parsing_stack.include?(blueprint.name)
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
elsif @parsing_stack.include?([blueprint.name, association.view])
|
|
79
|
+
schema_key = "#{blueprint.name}_#{association.view}"
|
|
80
|
+
@root.schema_registry[schema_key] ||= nil
|
|
81
|
+
{ "$ref" => "#/components/schemas/#{schema_key}" }
|
|
74
82
|
else
|
|
75
|
-
build_schema(blueprint, false)
|
|
83
|
+
build_schema(blueprint, false, serializer_options)
|
|
76
84
|
end
|
|
77
85
|
|
|
78
86
|
properties[display_name] = is_collection ? { "type" => "array", "items" => item_schema } : item_schema
|
data/lib/rage/router/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
This is an almost complete rewrite of https://github.com/delvedor/find-my-way.
|
|
2
2
|
|
|
3
|
-
Currently, the only constraint supported is the `host` constraint.
|
|
3
|
+
Currently, the only constraint supported is the `host` constraint. The constraint value can be either a string or a regular expression. Custom/lambda constraints are unlikely to be added.
|
|
4
4
|
|
|
5
5
|
Compared to the Rails router, the most notable difference except constraints is that a wildcard segment can only be in the last section of the path and cannot be named.
|
|
6
6
|
|
data/lib/rage/router/backend.rb
CHANGED
|
@@ -35,13 +35,17 @@ class Rage::Router::Backend
|
|
|
35
35
|
# by the time the app is called, `rack.input` is already consumed in `Rage::ParamsParser`
|
|
36
36
|
env["rack.input"].rewind
|
|
37
37
|
|
|
38
|
-
env["SCRIPT_NAME"]
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
script_name = env["SCRIPT_NAME"]
|
|
39
|
+
path_info = env["PATH_INFO"]
|
|
40
|
+
|
|
41
|
+
env["SCRIPT_NAME"] = "#{script_name}#{path}"
|
|
42
|
+
sub_path = path_info.delete_prefix(path)
|
|
43
|
+
env["PATH_INFO"] = sub_path == "" ? "/" : sub_path
|
|
41
44
|
|
|
42
45
|
handler.call(env)
|
|
43
46
|
ensure
|
|
44
|
-
env["
|
|
47
|
+
env["SCRIPT_NAME"] = script_name
|
|
48
|
+
env["PATH_INFO"] = path_info
|
|
45
49
|
end
|
|
46
50
|
|
|
47
51
|
methods.each do |method|
|
data/lib/rage/router/dsl.rb
CHANGED
|
@@ -420,11 +420,11 @@ class Rage::Router::DSL
|
|
|
420
420
|
at = at.delete_suffix("/") if at.end_with?("/")
|
|
421
421
|
|
|
422
422
|
http_methods = if via == :all || via.nil?
|
|
423
|
-
@default_match_methods.map { |method| method.to_s.upcase
|
|
423
|
+
@default_match_methods.map { |method| method.to_s.upcase }
|
|
424
424
|
else
|
|
425
|
-
Array(via).map
|
|
425
|
+
Array(via).map do |method|
|
|
426
426
|
raise ArgumentError, "Invalid HTTP method: #{method}" unless @default_match_methods.include?(method)
|
|
427
|
-
method.to_s.upcase
|
|
427
|
+
method.to_s.upcase
|
|
428
428
|
end
|
|
429
429
|
end
|
|
430
430
|
|
|
@@ -501,8 +501,7 @@ class Rage::Router::DSL
|
|
|
501
501
|
end
|
|
502
502
|
|
|
503
503
|
def to_singular(str)
|
|
504
|
-
|
|
505
|
-
return str.singularize if @active_support_loaded != :false
|
|
504
|
+
return str.singularize if str.respond_to?(:singularize)
|
|
506
505
|
|
|
507
506
|
@endings ||= {
|
|
508
507
|
"ves" => "fe",
|
|
@@ -519,8 +518,7 @@ class Rage::Router::DSL
|
|
|
519
518
|
end
|
|
520
519
|
|
|
521
520
|
def to_plural(str)
|
|
522
|
-
|
|
523
|
-
return str.pluralize if @active_support_loaded != :false
|
|
521
|
+
return str.pluralize if str.respond_to?(:pluralize)
|
|
524
522
|
|
|
525
523
|
str.end_with?("s") ? str : "#{str}s"
|
|
526
524
|
end
|
data/lib/rage/tasks.rb
CHANGED
|
@@ -7,6 +7,7 @@ class Rage::Tasks
|
|
|
7
7
|
class << self
|
|
8
8
|
def init
|
|
9
9
|
load_db_tasks if defined?(StandaloneMigrations)
|
|
10
|
+
load_rage_tasks
|
|
10
11
|
load_app_tasks
|
|
11
12
|
end
|
|
12
13
|
|
|
@@ -40,5 +41,9 @@ class Rage::Tasks
|
|
|
40
41
|
def load_app_tasks
|
|
41
42
|
Dir[Rage.root.join("lib/tasks/**/*.rake")].each { |file| load file }
|
|
42
43
|
end
|
|
44
|
+
|
|
45
|
+
def load_rage_tasks
|
|
46
|
+
Dir[File.expand_path("tasks/**/*.rake", __dir__)].each { |file| load file }
|
|
47
|
+
end
|
|
43
48
|
end
|
|
44
49
|
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rage::Telemetry
|
|
4
|
+
##
|
|
5
|
+
# The `Rage::Telemetry::Capacity` module exposes framework-owned runtime
|
|
6
|
+
# state - metrics describing the server's current load and resource utilization
|
|
7
|
+
# that cannot be derived from existing spans.
|
|
8
|
+
#
|
|
9
|
+
# Unlike spans, capacity metrics aren't tied to a specific operation or
|
|
10
|
+
# event, they reflect the server state at the moment they are read, and
|
|
11
|
+
# are meant to be sampled periodically rather than triggered by handlers.
|
|
12
|
+
#
|
|
13
|
+
# Combine with {Rage::Telemetry.every} to report a metric on a fixed
|
|
14
|
+
# interval:
|
|
15
|
+
#
|
|
16
|
+
# Rage::Telemetry.every(1000) do
|
|
17
|
+
# MyMetrics.gauge("server.queued_connections", Rage::Telemetry::Capacity.queued_connections)
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# @see Rage::Telemetry.every
|
|
21
|
+
#
|
|
22
|
+
module Capacity
|
|
23
|
+
class << self
|
|
24
|
+
# Returns the number of established connections currently waiting in the
|
|
25
|
+
# kernel accept queue for the server listening sockets. This is the
|
|
26
|
+
# count of clients that have already completed the TCP handshake but
|
|
27
|
+
# haven't yet been picked up by the application via `accept()`.
|
|
28
|
+
#
|
|
29
|
+
# A value that stays bigger than 0 is a sign the server isn't accepting
|
|
30
|
+
# connections fast enough to keep up with incoming traffic.
|
|
31
|
+
#
|
|
32
|
+
# @raise [NotImplementedError] on non-Linux systems
|
|
33
|
+
# @return [Integer] the accept-queue depth
|
|
34
|
+
def queued_connections
|
|
35
|
+
accept_queue_depth = Iodine::Perf.queued_connections
|
|
36
|
+
raise NotImplementedError if accept_queue_depth.nil?
|
|
37
|
+
|
|
38
|
+
accept_queue_depth
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -36,6 +36,24 @@ module Rage::Telemetry
|
|
|
36
36
|
__registry.keys
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
# Registers a block to be executed repeatedly at a fixed interval while the server is running.
|
|
40
|
+
# The block runs inside a fiber, so blocking I/O inside it (e.g. flushing metrics to a
|
|
41
|
+
# collector) will not block the server.
|
|
42
|
+
#
|
|
43
|
+
# @param interval_ms [Integer] the execution interval in milliseconds
|
|
44
|
+
# @example Periodically sample GC stats
|
|
45
|
+
# Rage::Telemetry.every(1000) { MyMetrics.record_gc_stats(GC.stat) }
|
|
46
|
+
def self.every(interval_ms, &block)
|
|
47
|
+
Iodine.run_every(interval_ms) do
|
|
48
|
+
Fiber.schedule do
|
|
49
|
+
block.call
|
|
50
|
+
rescue => e
|
|
51
|
+
Rage.logger.error("#{e.class} (#{e.message}):\n#{e.backtrace.join("\n")}")
|
|
52
|
+
Rage::Errors.report(e)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
39
57
|
# @private
|
|
40
58
|
def self.__registry
|
|
41
59
|
@__registry ||= Spans.constants.each_with_object({}) do |const, memo|
|
|
@@ -121,4 +139,5 @@ end
|
|
|
121
139
|
|
|
122
140
|
require_relative "tracer"
|
|
123
141
|
require_relative "handler"
|
|
142
|
+
require_relative "capacity"
|
|
124
143
|
Dir["#{__dir__}/spans/*.rb"].each { |span| require_relative span }
|
data/lib/rage/version.rb
CHANGED
data/lib/rage-rb.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rage-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.28.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Roman Samoilov
|
|
@@ -43,14 +43,14 @@ dependencies:
|
|
|
43
43
|
requirements:
|
|
44
44
|
- - "~>"
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '5.
|
|
46
|
+
version: '5.6'
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
51
|
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '5.
|
|
53
|
+
version: '5.6'
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
55
|
name: zeitwerk
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -152,6 +152,10 @@ files:
|
|
|
152
152
|
- lib/rage/cable/protocols/raw_web_socket_json.rb
|
|
153
153
|
- lib/rage/cable/router.rb
|
|
154
154
|
- lib/rage/cli.rb
|
|
155
|
+
- lib/rage/cli/base.rb
|
|
156
|
+
- lib/rage/cli/code_generator.rb
|
|
157
|
+
- lib/rage/cli/new_app_generator.rb
|
|
158
|
+
- lib/rage/cli/openapi.rb
|
|
155
159
|
- lib/rage/cli/skills.rb
|
|
156
160
|
- lib/rage/code_loader.rb
|
|
157
161
|
- lib/rage/configuration.rb
|
|
@@ -233,6 +237,7 @@ files:
|
|
|
233
237
|
- lib/rage/sse/sse.rb
|
|
234
238
|
- lib/rage/sse/stream.rb
|
|
235
239
|
- lib/rage/tasks.rb
|
|
240
|
+
- lib/rage/telemetry/capacity.rb
|
|
236
241
|
- lib/rage/telemetry/handler.rb
|
|
237
242
|
- lib/rage/telemetry/spans/await_fiber.rb
|
|
238
243
|
- lib/rage/telemetry/spans/broadcast_cable_stream.rb
|