async-tools 0.2.6 → 0.2.8

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: 0e6e1090999fdea25dd828cc680fcf23c2a689b9d4e496c5a3b7cff917ea9a34
4
- data.tar.gz: 9d9e7828f26eb1e31da2d0f4ca7cc9912614b8fe69a6e5f61e830f0be0cab6b6
3
+ metadata.gz: '0906d98b66177300144f1f52414ab79d077bfd75874ba4ebdee3bcac7c12f91b'
4
+ data.tar.gz: 26b18c911296ac5ea3ac5673417b9fef92e83eae475a5ac7ba79a4037e0a7c91
5
5
  SHA512:
6
- metadata.gz: 777703b396852748b6113c3b2e5b201838cf7be184b7c00693f6e21bbbca7e08ac99ca09b43932719a2273985ef218afcb9803f38618ca520bf206db1f328234
7
- data.tar.gz: 321d4d42cdce361e467c6f9e5bb94e00ad7c02694628ca7851b2997f09e7cdbdaa8e435c60f15e9cdeebb6490235d48b412e08bf6de9fb9fd2d2fa46996f4d01
6
+ metadata.gz: 5b3f48863641bd02bd74d58267c5c3f1c44023d25ca8f06df50daec535913118d5ff121e6d3f38f158d41f365001aa81e80d2353ee0abdf17c2028a9ff4fdcf8
7
+ data.tar.gz: 86128a933957cf1efae7f67e47361cc02c72201836abf715db193b40cc36300bda9fb9ee7b0fafd9e1555ea57afd9f8a39f87da23fced2786cc3057487551448
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- async-tools (0.2.6)
4
+ async-tools (0.2.8)
5
5
  async (~> 2.3)
6
6
  zeitwerk (~> 2.6)
7
7
 
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Async::App::Metrics::RubyRuntimeMetricsCollector
4
+ include Async::App::Component
5
+
6
+ INTERVAL = 5
7
+
8
+ def run
9
+ Async::Timer.new(INTERVAL, run_on_start: true, on_error: ->(e) { warn(e) }) do
10
+ bus.publish("metrics.updated", metrics)
11
+ end
12
+ info { "Started" }
13
+ end
14
+
15
+ def metrics
16
+ fibers = ObjectSpace.each_object(Fiber)
17
+ threads = ObjectSpace.each_object(Thread)
18
+ ractors = ObjectSpace.each_object(Ractor)
19
+ {
20
+ ruby_fibers: { value: fibers.count },
21
+ ruby_fibers_active: { value: fibers.count(&:alive?) },
22
+ ruby_threads: { value: threads.count },
23
+ ruby_threads_active: { value: threads.count(&:alive?) },
24
+ ruby_ractors: { value: ractors.count },
25
+ ruby_memory: { value: GetProcessMem.new.bytes.to_s("F"), suffix: "bytes" }
26
+ }
27
+ end
28
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Async::App::WebServer::HealthApp
4
+ include Async::App::Component
5
+
6
+ PATHS = ["/health", "/health/"].freeze
7
+
8
+ def initialize
9
+ @healthy = false
10
+
11
+ bus.subscribe("health.updated") { @healthy = _1 }
12
+ end
13
+
14
+ def can_handle?(request) = PATHS.include?(request.path)
15
+ def call(_) = [@healthy ? 200 : 500]
16
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Async::App::WebServer::MetricsApp::Serializer
4
+ def initialize(prefix:, store:)
5
+ @prefix = prefix
6
+ @store = store
7
+ end
8
+
9
+ def serialize
10
+ @store.flat_map { metric_line(_1) }
11
+ .compact
12
+ .join("\n")
13
+ .then { "#{_1}\n" }
14
+ end
15
+
16
+ def metric_name(value) = "#{@prefix}_#{value[:name]}_#{value[:suffix]}"
17
+
18
+ def metric_labels(value) = value[:labels].map { |tag, tag_value| "#{tag}=#{tag_value.to_s.inspect}" }.join(",")
19
+
20
+ def metric_line(value)
21
+ "#{metric_name(value)}{#{metric_labels(value)}} #{value[:value]}" if value.key?(:value)
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Async::App::WebServer::MetricsApp::Store
4
+ include Enumerable
5
+
6
+ def set(name, value:, suffix: "total", **labels)
7
+ key = [name, labels]
8
+ metrics[key] ||= { name:, labels:, suffix:, value: }
9
+ metrics[key].merge!(value:)
10
+ end
11
+
12
+ def each(&) = metrics.values.each(&)
13
+
14
+ private
15
+
16
+ def metrics = @metrics ||= {}
17
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Async::App::WebServer::MetricsApp
4
+ include Async::App::Component
5
+
6
+ PATHS = ["/metrics", "/metrics/"].freeze
7
+
8
+ def initialize(metrics_prefix:)
9
+ store = Store.new
10
+ @serializer = Serializer.new(prefix: metrics_prefix, store:)
11
+
12
+ bus.subscribe("metrics.updated") do |metrics|
13
+ metrics.each { store.set(_1, **_2) }
14
+ end
15
+ end
16
+
17
+ def can_handle?(request) = PATHS.include?(request.path)
18
+ def call(*) = [200, {}, @serializer.serialize]
19
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Async::App::WebServer::Router
4
+ extend Async::App::Injector
5
+
6
+ def initialize(*apps)
7
+ @apps = apps
8
+ end
9
+
10
+ def call(request)
11
+ @apps.each { return Protocol::HTTP::Response[*_1.call(request)] if _1.can_handle?(request) }
12
+
13
+ Protocol::HTTP::Response[404, {}, ["Not found"]]
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Async::App::WebServer
4
+ include Async::Logger
5
+
6
+ def initialize(metrics_prefix:, port: 8080)
7
+ @router = Async::App::WebServer::Router.new(
8
+ MetricsApp.new(metrics_prefix:),
9
+ HealthApp.new
10
+ )
11
+ @endpoint = Async::HTTP::Endpoint.parse("http://0.0.0.0:#{port}")
12
+ end
13
+
14
+ def run
15
+ Async { Async::HTTP::Server.new(@router, @endpoint).run }
16
+ info { "Started on #{@endpoint.url}" }
17
+ end
18
+ end
data/lib/async/app.rb CHANGED
@@ -18,15 +18,19 @@ class Async::App
18
18
  init_container!
19
19
 
20
20
  start_event_logger!
21
- start_metrics_server!
21
+ start_web_server!
22
+
23
+ start_runtime_metrics_collector!
24
+
22
25
  run!
26
+
23
27
  info { "Started" }
28
+ bus.publish("health.updated", true)
24
29
  rescue StandardError => e
25
30
  fatal { e }
26
31
  stop
27
32
  exit(1)
28
33
  end
29
- # rubocop:enable Style/GlobalVars
30
34
 
31
35
  def container = @container ||= Dry::Container.new
32
36
  def run! = nil
@@ -35,9 +39,12 @@ class Async::App
35
39
 
36
40
  def stop
37
41
  @task&.stop
42
+ $__ASYNC_APP = nil
38
43
  info { "Stopped" }
39
44
  end
40
45
 
46
+ # rubocop:enable Style/GlobalVars
47
+
41
48
  private
42
49
 
43
50
  def set_traps!
@@ -63,11 +70,7 @@ class Async::App
63
70
  exit(1)
64
71
  end
65
72
 
66
- def start_metrics_server!
67
- Metrics::Server.new(prefix: app_name).tap(&:run).tap do |server|
68
- bus.subscribe("metrics.updated") { server.update_metrics(_1) }
69
- end
70
- end
71
-
73
+ def start_web_server! = WebServer.new(metrics_prefix: app_name).run
72
74
  def start_event_logger! = EventLogger.new.run
75
+ def start_runtime_metrics_collector! = Async::App::Metrics::RubyRuntimeMetricsCollector.new.run
73
76
  end
data/lib/async/bus.rb CHANGED
@@ -19,12 +19,12 @@ class Async::Bus
19
19
  end
20
20
 
21
21
  # NON-BLOCKING, runs subscriber in a task
22
- def async_subscribe(name, parent: Async::Task.current)
23
- subscribe(name) do |event|
22
+ def async_subscribe(pattern, parent: Async::Task.current)
23
+ subscribe(pattern) do |event|
24
24
  parent.async do
25
25
  yield(event)
26
26
  rescue StandardError => e
27
- log_error(name, e)
27
+ log_error(pattern, e)
28
28
  end
29
29
  end
30
30
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Async # rubocop:disable Style/ClassAndModuleChildren
4
4
  module Tools
5
- VERSION = "0.2.6"
5
+ VERSION = "0.2.8"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.6
4
+ version: 0.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gleb Sinyavskiy
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-03-07 00:00:00.000000000 Z
11
+ date: 2023-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async
@@ -64,10 +64,13 @@ files:
64
64
  - lib/async/app/component.rb
65
65
  - lib/async/app/event_logger.rb
66
66
  - lib/async/app/injector.rb
67
- - lib/async/app/metrics/ruby_runtime_monitor.rb
68
- - lib/async/app/metrics/serializer.rb
69
- - lib/async/app/metrics/server.rb
70
- - lib/async/app/metrics/store.rb
67
+ - lib/async/app/metrics/ruby_runtime_metrics_collector.rb
68
+ - lib/async/app/web_server.rb
69
+ - lib/async/app/web_server/health_app.rb
70
+ - lib/async/app/web_server/metrics_app.rb
71
+ - lib/async/app/web_server/metrics_app/serializer.rb
72
+ - lib/async/app/web_server/metrics_app/store.rb
73
+ - lib/async/app/web_server/router.rb
71
74
  - lib/async/bus.rb
72
75
  - lib/async/cache.rb
73
76
  - lib/async/channel.rb
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Async::App::Metrics::RubyRuntimeMonitor
4
- include Async::Logger
5
-
6
- INTERVAL = 2
7
-
8
- def run
9
- Async::Timer.new(INTERVAL, run_on_start: true, on_error: ->(e) { warn(e) }) do
10
- fibers = ObjectSpace.each_object(Fiber)
11
- threads = ObjectSpace.each_object(Thread)
12
- ractors = ObjectSpace.each_object(Ractor)
13
-
14
- yield({
15
- ruby_fibers: { value: fibers.count },
16
- ruby_fibers_active: { value: fibers.count(&:alive?) },
17
- ruby_threads: { value: threads.count },
18
- ruby_threads_active: { value: threads.count(&:alive?) },
19
- ruby_ractors: { value: ractors.count },
20
- ruby_memory: { value: GetProcessMem.new.bytes.to_s("F"), suffix: "bytes" }
21
- })
22
- end
23
- info { "Started" }
24
- end
25
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Async::App::Metrics::Serializer
4
- def initialize(prefix:)
5
- @prefix = prefix
6
- end
7
-
8
- def serialize(metrics)
9
- metrics.flat_map { metric_line(_1) }
10
- .compact
11
- .join("\n")
12
- .then { "#{_1}\n" }
13
- end
14
-
15
- def metric_name(value) = "#{@prefix}_#{value[:name]}_#{value[:suffix]}"
16
-
17
- def metric_labels(value) = value[:labels].map { |tag, tag_value| "#{tag}=#{tag_value.to_s.inspect}" }.join(",")
18
-
19
- def metric_line(value)
20
- labels = metric_labels(value)
21
-
22
- "#{metric_name(value)}{#{labels}} #{value[:value]}" if value.key?(:value)
23
- end
24
- end
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Async::App::Metrics::Server
4
- include Async::Logger
5
-
6
- PATHS = ["/metrics", "/metrics/"].freeze
7
-
8
- def initialize(prefix:, port: 8080)
9
- @prefix = prefix
10
- @port = port
11
- end
12
-
13
- def run
14
- Async::App::Metrics::RubyRuntimeMonitor.new.run { update_metrics(_1) }
15
-
16
- endpoint = Async::HTTP::Endpoint.parse("http://0.0.0.0:#{@port}")
17
- Async { Async::HTTP::Server.new(self, endpoint).run }
18
- info { "Started on #{endpoint.url}" }
19
- end
20
-
21
- def call(request)
22
- return Protocol::HTTP::Response[404, {}, ["Not found"]] unless PATHS.include?(request.path)
23
-
24
- Protocol::HTTP::Response[200, {}, serializer.serialize(metrics_store)]
25
- end
26
-
27
- def update_metrics(metrics) = metrics.each { metrics_store.set(_1, **_2) }
28
-
29
- private
30
-
31
- def metrics_store = @metrics_store ||= Async::App::Metrics::Store.new
32
- def serializer = @serializer ||= Async::App::Metrics::Serializer.new(prefix: @prefix)
33
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Async::App::Metrics::Store
4
- include Enumerable
5
-
6
- def set(name, value:, suffix: "total", **labels)
7
- key = [name, labels]
8
- counters[key] ||= { name:, labels:, suffix:, value: }
9
- counters[key].merge!(value:)
10
- end
11
-
12
- def each(&) = counters.values.each(&)
13
-
14
- private
15
-
16
- def counters = @counters ||= {}
17
- end