async-tools 0.2.1 → 0.2.2

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: 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