async-background 0.2.6 → 0.3.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/lib/async/background/metrics.rb +143 -0
- data/lib/async/background/runner.rb +10 -2
- data/lib/async/background/version.rb +1 -1
- data/lib/async/background.rb +2 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 00b7ba6035c743fb4c4568d2309da4bc7f454a3cbe5e17ac5d2251a417a4a31c
|
|
4
|
+
data.tar.gz: a3dff9b2b51f590a095120a18f1e3d5424b339ab5a6cff90b148f8e9316ee242
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b9c36893b1cb14ba251d9c5a1c614ac50b988ffcbd69c43a876c1e0a2995ee749592618c2e328d2e05573ccf46186616a59615d7c01a785b28fe5897a1e0650f
|
|
7
|
+
data.tar.gz: 3f47cf30226b45b6c543f48812e95778206e9cccc07445c5066e32cdf88a71f68bf88be9f0134750344cd7e0ed8df1a907107d0e5a3c910ce00a3a493ea700e7
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Async
|
|
4
|
+
module Background
|
|
5
|
+
class Metrics
|
|
6
|
+
SCHEMA_FIELDS = {
|
|
7
|
+
total_runs: :u64,
|
|
8
|
+
total_successes: :u64,
|
|
9
|
+
total_failures: :u64,
|
|
10
|
+
total_timeouts: :u64,
|
|
11
|
+
total_skips: :u64,
|
|
12
|
+
active_jobs: :u32,
|
|
13
|
+
last_run_at: :u64,
|
|
14
|
+
last_duration_ms: :u32
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
attr_reader :registry
|
|
18
|
+
|
|
19
|
+
def initialize(worker_index:, total_workers:, shm_path: self.class.default_shm_path)
|
|
20
|
+
require 'async/utilization'
|
|
21
|
+
|
|
22
|
+
@registry = nil
|
|
23
|
+
@enabled = false
|
|
24
|
+
@registry = ::Async::Utilization::Registry.new
|
|
25
|
+
@enabled = true
|
|
26
|
+
|
|
27
|
+
ensure_shm!(total_workers, shm_path)
|
|
28
|
+
attach_observer!(worker_index, total_workers, shm_path)
|
|
29
|
+
rescue LoadError
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def enabled?
|
|
33
|
+
@enabled
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def job_started(entry)
|
|
37
|
+
return unless @enabled
|
|
38
|
+
|
|
39
|
+
@registry.increment(:total_runs)
|
|
40
|
+
@registry.increment(:active_jobs)
|
|
41
|
+
@registry.set(:last_run_at, Process.clock_gettime(Process::CLOCK_REALTIME).to_i)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def job_finished(entry, duration)
|
|
45
|
+
return unless @enabled
|
|
46
|
+
|
|
47
|
+
@registry.decrement(:active_jobs)
|
|
48
|
+
@registry.increment(:total_successes)
|
|
49
|
+
@registry.set(:last_duration_ms, (duration * 1000).to_i)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def job_failed(entry, error)
|
|
53
|
+
return unless @enabled
|
|
54
|
+
|
|
55
|
+
@registry.decrement(:active_jobs)
|
|
56
|
+
@registry.increment(:total_failures)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def job_timed_out(entry)
|
|
60
|
+
return unless @enabled
|
|
61
|
+
|
|
62
|
+
@registry.decrement(:active_jobs)
|
|
63
|
+
@registry.increment(:total_timeouts)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def job_skipped(entry)
|
|
67
|
+
return unless @enabled
|
|
68
|
+
|
|
69
|
+
@registry.increment(:total_skips)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def values
|
|
73
|
+
return {} unless @enabled
|
|
74
|
+
|
|
75
|
+
@registry.values
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def self.schema
|
|
79
|
+
require 'async/utilization'
|
|
80
|
+
::Async::Utilization::Schema.build(SCHEMA_FIELDS)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Read metrics for all workers from the shm file.
|
|
84
|
+
# No server needed — just reads the mmap'd file.
|
|
85
|
+
#
|
|
86
|
+
# Async::Background::Metrics.read_all(total_workers: 2)
|
|
87
|
+
# # => [
|
|
88
|
+
# # { worker: 1, total_runs: 142, active_jobs: 1, ... },
|
|
89
|
+
# # { worker: 2, total_runs: 98, active_jobs: 0, ... }
|
|
90
|
+
# # ]
|
|
91
|
+
#
|
|
92
|
+
def self.read_all(total_workers:, path: default_shm_path)
|
|
93
|
+
require 'async/utilization'
|
|
94
|
+
|
|
95
|
+
s = schema
|
|
96
|
+
segment = segment_size
|
|
97
|
+
file_size = segment * total_workers
|
|
98
|
+
|
|
99
|
+
buffer = File.open(path, "rb") do |f|
|
|
100
|
+
IO::Buffer.map(f, file_size, 0)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
(1..total_workers).map do |i|
|
|
104
|
+
base = (i - 1) * segment
|
|
105
|
+
row = { worker: i }
|
|
106
|
+
s.fields.each do |field|
|
|
107
|
+
row[field.name] = buffer.get_value(field.type, base + field.offset)
|
|
108
|
+
end
|
|
109
|
+
row
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def self.default_shm_path
|
|
114
|
+
File.join(Dir.tmpdir, "async-background.shm")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def self.segment_size
|
|
118
|
+
SCHEMA_FIELDS.sum { |_, type| IO::Buffer.size_of(type) }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
def ensure_shm!(total_workers, path)
|
|
124
|
+
required = self.class.segment_size * total_workers
|
|
125
|
+
|
|
126
|
+
File.open(path, File::CREAT | File::RDWR, 0644) do |f|
|
|
127
|
+
f.flock(File::LOCK_EX)
|
|
128
|
+
f.truncate(required) if f.size < required
|
|
129
|
+
f.flock(File::LOCK_UN)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def attach_observer!(worker_index, total_workers, path)
|
|
134
|
+
segment = self.class.segment_size
|
|
135
|
+
offset = (worker_index - 1) * segment
|
|
136
|
+
observer = ::Async::Utilization::Observer.open(
|
|
137
|
+
self.class.schema, path, segment, offset
|
|
138
|
+
)
|
|
139
|
+
@registry.observer = observer
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -12,7 +12,7 @@ module Async
|
|
|
12
12
|
MAX_JITTER = 5
|
|
13
13
|
|
|
14
14
|
class Runner
|
|
15
|
-
attr_reader :logger, :semaphore, :heap, :worker_index, :total_workers, :shutdown
|
|
15
|
+
attr_reader :logger, :semaphore, :heap, :worker_index, :total_workers, :shutdown, :metrics
|
|
16
16
|
|
|
17
17
|
def initialize(config_path:, job_count: 2, worker_index:, total_workers:)
|
|
18
18
|
@logger = Console.logger
|
|
@@ -20,6 +20,7 @@ module Async
|
|
|
20
20
|
@total_workers = total_workers
|
|
21
21
|
@running = true
|
|
22
22
|
@shutdown = ::Async::Condition.new
|
|
23
|
+
@metrics = Metrics.new(worker_index: worker_index, total_workers: total_workers)
|
|
23
24
|
|
|
24
25
|
logger.info { "Async::Background worker_index=#{worker_index}/#{total_workers}, job_count=#{job_count}" }
|
|
25
26
|
|
|
@@ -47,6 +48,7 @@ module Async
|
|
|
47
48
|
|
|
48
49
|
if entry.running
|
|
49
50
|
logger.warn('Async::Background') { "#{entry.name}: skipped, previous run still active" }
|
|
51
|
+
metrics.job_skipped(entry)
|
|
50
52
|
else
|
|
51
53
|
entry.running = true
|
|
52
54
|
semaphore.async do
|
|
@@ -180,14 +182,20 @@ module Async
|
|
|
180
182
|
end
|
|
181
183
|
|
|
182
184
|
def run_job(task, entry)
|
|
185
|
+
metrics.job_started(entry)
|
|
183
186
|
t = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
184
187
|
task.with_timeout(entry.timeout) { entry.job_class.perform_now }
|
|
188
|
+
|
|
189
|
+
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - t
|
|
190
|
+
metrics.job_finished(entry, duration)
|
|
185
191
|
logger.info('Async::Background') {
|
|
186
|
-
"#{entry.name}: completed in #{
|
|
192
|
+
"#{entry.name}: completed in #{duration.round(2)}s"
|
|
187
193
|
}
|
|
188
194
|
rescue ::Async::TimeoutError
|
|
195
|
+
metrics.job_timed_out(entry)
|
|
189
196
|
logger.error('Async::Background') { "#{entry.name}: timed out after #{entry.timeout}s" }
|
|
190
197
|
rescue => e
|
|
198
|
+
metrics.job_failed(entry, e)
|
|
191
199
|
logger.error('Async::Background') {
|
|
192
200
|
"#{entry.name}: #{e.class} #{e.message}\n#{e.backtrace.join("\n")}"
|
|
193
201
|
}
|
data/lib/async/background.rb
CHANGED
|
@@ -4,8 +4,10 @@ require 'async'
|
|
|
4
4
|
require 'async/semaphore'
|
|
5
5
|
require 'console'
|
|
6
6
|
require 'fugit'
|
|
7
|
+
require 'tmpdir'
|
|
7
8
|
|
|
8
9
|
require_relative 'background/version'
|
|
9
10
|
require_relative 'background/min_heap'
|
|
10
11
|
require_relative 'background/entry'
|
|
12
|
+
require_relative 'background/metrics'
|
|
11
13
|
require_relative 'background/runner'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: async-background
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Roman Hajdarov
|
|
@@ -92,6 +92,7 @@ extra_rdoc_files: []
|
|
|
92
92
|
files:
|
|
93
93
|
- lib/async/background.rb
|
|
94
94
|
- lib/async/background/entry.rb
|
|
95
|
+
- lib/async/background/metrics.rb
|
|
95
96
|
- lib/async/background/min_heap.rb
|
|
96
97
|
- lib/async/background/runner.rb
|
|
97
98
|
- lib/async/background/version.rb
|