async-tools 0.2.7 → 0.2.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c5857bd13503604e3465c92d121e209424e5db7bca05b50c8136b59a9880f185
4
- data.tar.gz: 459533340df4832809b5e8b03558cef26bc8d580f6dc9169904e4b5d430817c5
3
+ metadata.gz: fc3694f2f03f7c7845ee1c6911a3a2f2042b0d4b55c20fdef6c616e205c45c64
4
+ data.tar.gz: d7c56b6cb80b8dc83a041f3f53bc5b876b0f2ea6286bf6b53dd3c0173b264e7d
5
5
  SHA512:
6
- metadata.gz: e8615f9092884f2376c37200d90bf029f5d79948e7fd61eec4bc5f7eb37f567ab52ed10734aba02367c3c9916ea7b83a4dc357be0956a3c47aedcede1ae16935
7
- data.tar.gz: 94900e33d70558be801b8becb7ed59a731d617aa1c7decaebfc09da1c1b0dc395f87b32f29b71c297ce28350b008072ba7fa30b5bafeb2d2ce9f7822c0df6d9c
6
+ metadata.gz: 42cc9c27e20fca3fd48287c0c0f0bd5c4176d607d540d9e3ecb5237eebecf494ffb4fe8dc9b025e85ed45771bedadfa3b227fb702a344fe11bf42e0ca212699a
7
+ data.tar.gz: ff2dcf6780a51aad4e220dbb0548a0757dcc603072b1c7b195b7124e9175e14a31300b349d1dfde1868020511317182ad07074499ea94835424e08ebed59e1dd
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- async-tools (0.2.7)
4
+ async-tools (0.2.9)
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,9 +18,14 @@ 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
@@ -65,11 +70,7 @@ class Async::App
65
70
  exit(1)
66
71
  end
67
72
 
68
- def start_metrics_server!
69
- Metrics::Server.new(prefix: app_name).tap(&:run).tap do |server|
70
- bus.subscribe("metrics.updated") { server.update_metrics(_1) }
71
- end
72
- end
73
-
73
+ def start_web_server! = WebServer.new(metrics_prefix: app_name).run
74
74
  def start_event_logger! = EventLogger.new.run
75
+ def start_runtime_metrics_collector! = Async::App::Metrics::RubyRuntimeMetricsCollector.new.run
75
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
data/lib/async/timer.rb CHANGED
@@ -27,14 +27,13 @@ class Async::Timer
27
27
  self.start if start
28
28
  end
29
29
 
30
- def stop = @task.stop
30
+ def stop = @task.stop(true)
31
31
  def call = @callable.call
32
32
 
33
33
  def active? = @active
34
34
 
35
35
  def restart
36
36
  stop
37
- @task.wait
38
37
  start
39
38
  end
40
39
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Async # rubocop:disable Style/ClassAndModuleChildren
4
4
  module Tools
5
- VERSION = "0.2.7"
5
+ VERSION = "0.2.9"
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.7
4
+ version: 0.2.9
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