async-tools 0.2.1 → 0.2.2

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: 37e9af668ebad3f0c2356cc2a428b1bedf8b432e239099dd1b961f16d0b01036
4
- data.tar.gz: 201bc5e60a2b19e5a1b1498c14c0e792e96bd21a688b3605f443e6f98a2b6f29
3
+ metadata.gz: 8b3b08f9b120bebf744df3e7c0da6e2cb7c8d4c5acb01458c25f8be8ff7588fa
4
+ data.tar.gz: 7c02d0bfce7a3bcb21b8ac8b2f4a8aec4bfad5a12233141b7d1d73b139c08684
5
5
  SHA512:
6
- metadata.gz: f92056eef4bc4dc65210c682b8637fc6e39088b1213cca0f4b9bf54a06a5b736ce841c351bbaf211bac3777ccda8a7220ee2a926d822e54758a83e87b5c856f8
7
- data.tar.gz: e463ee25e793a281c1130ede835c427f03c47f15bd51d708f28396533652ef6be14e7ecd0b08b577ec2dadf73e8ec90522bc986a6911b64ea1f049c3c5cd264e
6
+ metadata.gz: 496680113c44a54745da6846bca478f426454eaa34c125de569205c9f5fe3bc82ec6fe53589f8c86c0b1222361ec949c7938baa64b2be0246a15da280ca8f30a
7
+ data.tar.gz: 14d6d23403ede1040d74e68590ad0572e1c01b402ff6d7d0ff25dfb11c6f0f7a187b894c5681772e7828c88a8ba5f2e95751a3bce58ba9d9dc2aa14c31078e67
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- async-tools (0.2.1)
4
+ async-tools (0.2.2)
5
5
  async (~> 2.3)
6
6
  zeitwerk (~> 2.6)
7
7
 
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Async::App::Component
4
+ def self.included(base)
5
+ base.extend(Async::App::Injector)
6
+ base.inject(:bus)
7
+
8
+ base.include(Async::Logger)
9
+
10
+ strict = Dry.Types::Strict
11
+
12
+ string_like = (strict::String | strict::Symbol).constructor(&:to_s)
13
+ kv = strict::Hash.map(string_like, strict::String)
14
+
15
+ base.const_set(:T, Module.new do
16
+ include Dry.Types
17
+ const_set(:StringLike, string_like)
18
+ const_set(:KV, kv)
19
+ end)
20
+ end
21
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Async::App::Injector
4
+ def inject(name)
5
+ define_method(name) do
6
+ $__ASYNC_APP.container[name] # rubocop:disable Style/GlobalVars
7
+ end
8
+ private name
9
+ end
10
+ end
@@ -0,0 +1,25 @@
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
@@ -0,0 +1,24 @@
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
@@ -0,0 +1,33 @@
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
@@ -0,0 +1,17 @@
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
data/lib/async/app.rb CHANGED
@@ -1,52 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Async::App
4
- # rubocop:disable Style/GlobalVars
5
-
6
- module Injector
7
- def inject(name)
8
- define_method(name) do
9
- $__ASYNC_APP.container[name]
10
- end
11
- private name
12
- end
13
- end
14
-
15
- module Component
16
- def self.included(base)
17
- base.extend(Injector)
18
- base.inject(:bus)
4
+ extend Async::App::Injector
19
5
 
20
- base.include(Async::Logger)
21
-
22
- strict = Dry.Types::Strict
23
-
24
- string_like = (strict::String | strict::Symbol).constructor(&:to_s)
25
- kv = strict::Hash.map(string_like, strict::String)
26
-
27
- base.const_set(:T, Module.new do
28
- include Dry.Types
29
- const_set(:StringLike, string_like)
30
- const_set(:KV, kv)
31
- end)
32
- end
33
- end
34
-
35
- extend Injector
36
6
  include Async::Logger
37
7
 
38
8
  inject :bus
39
9
 
10
+ # rubocop:disable Style/GlobalVars
40
11
  def initialize
41
12
  raise "only one instance of #{self.class} is allowed" if $__ASYNC_APP
42
13
 
43
14
  $__ASYNC_APP = self
44
-
45
- container.register(:bus, Async::Bus.new(:__async_app))
15
+ @task = Async::Task.current
46
16
 
47
17
  set_traps!
48
- @task = Async::Task.current
49
- container_config.each { container.register(_1, _2) }
18
+ {
19
+ bus: Async::Bus.new(app_name),
20
+ **container_config
21
+ }.each { container.register(_1, _2) }
22
+
23
+ start_metrics_server!
50
24
  run!
51
25
  info { "Started" }
52
26
  rescue StandardError => e
@@ -59,6 +33,7 @@ class Async::App
59
33
  def container = @container ||= Dry::Container.new
60
34
  def run! = nil
61
35
  def container_config = {}
36
+ def app_name = :async_app
62
37
 
63
38
  def stop
64
39
  @task&.stop
@@ -82,4 +57,10 @@ class Async::App
82
57
  fatal { "Forced exit" }
83
58
  exit(1)
84
59
  end
60
+
61
+ def start_metrics_server!
62
+ Metrics::Server.new(prefix: app_name).tap(&:run).tap do |server|
63
+ bus.subscribe("metrics.updated") { server.update_metrics(_1) }
64
+ end
65
+ end
85
66
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Async # rubocop:disable Style/ClassAndModuleChildren
4
4
  module Tools
5
- VERSION = "0.2.1"
5
+ VERSION = "0.2.2"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gleb Sinyavskiy
@@ -61,6 +61,12 @@ files:
61
61
  - bin/console
62
62
  - bin/setup
63
63
  - lib/async/app.rb
64
+ - lib/async/app/component.rb
65
+ - lib/async/app/injector.rb
66
+ - lib/async/app/metrics/ruby_runtime_monitor.rb
67
+ - lib/async/app/metrics/serializer.rb
68
+ - lib/async/app/metrics/server.rb
69
+ - lib/async/app/metrics/store.rb
64
70
  - lib/async/bus.rb
65
71
  - lib/async/channel.rb
66
72
  - lib/async/logger.rb