aeternitas 0.2.0 → 2.0.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/.gitattributes +1 -0
- data/.github/workflows/lint.yml +25 -0
- data/.github/workflows/tests.yml +28 -0
- data/.gitignore +2 -5
- data/.ruby-version +1 -1
- data/CHANGELOG.md +36 -0
- data/CODE_OF_CONDUCT.md +0 -0
- data/Gemfile +1 -1
- data/LICENSE.txt +0 -0
- data/README.md +104 -145
- data/Rakefile +1 -1
- data/aeternitas.gemspec +23 -34
- data/lib/aeternitas/aeternitas_job.rb +7 -0
- data/lib/aeternitas/cleanup_old_metrics_job.rb +12 -0
- data/lib/aeternitas/cleanup_stale_locks_job.rb +12 -0
- data/lib/aeternitas/errors.rb +1 -2
- data/lib/aeternitas/guard.rb +76 -109
- data/lib/aeternitas/guard_lock.rb +19 -0
- data/lib/aeternitas/maintenance.rb +35 -0
- data/lib/aeternitas/metric.rb +12 -0
- data/lib/aeternitas/metrics.rb +54 -136
- data/lib/aeternitas/poll_job.rb +139 -0
- data/lib/aeternitas/pollable/configuration.rb +21 -22
- data/lib/aeternitas/pollable/dsl.rb +16 -17
- data/lib/aeternitas/pollable.rb +19 -18
- data/lib/aeternitas/pollable_meta_data.rb +18 -9
- data/lib/aeternitas/polling_frequency.rb +4 -4
- data/lib/aeternitas/source.rb +5 -5
- data/lib/aeternitas/storage_adapter/file.rb +9 -12
- data/lib/aeternitas/storage_adapter.rb +1 -3
- data/lib/aeternitas/test.rb +13 -0
- data/lib/aeternitas/unique_job_lock.rb +15 -0
- data/lib/aeternitas/version.rb +1 -1
- data/lib/aeternitas.rb +36 -26
- data/lib/generators/aeternitas/install_generator.rb +14 -8
- data/lib/generators/aeternitas/templates/add_aeternitas.rb.erb +34 -2
- data/lib/generators/aeternitas/templates/initializer.rb +10 -7
- metadata +39 -123
- data/.idea/.rakeTasks +0 -7
- data/.idea/misc.xml +0 -4
- data/.idea/modules.xml +0 -8
- data/.idea/vcs.xml +0 -6
- data/.rspec +0 -2
- data/.rubocop.yml +0 -2
- data/.travis.yml +0 -8
- data/lib/aeternitas/metrics/counter.rb +0 -18
- data/lib/aeternitas/metrics/ratio.rb +0 -67
- data/lib/aeternitas/metrics/ten_minutes_resolution.rb +0 -40
- data/lib/aeternitas/metrics/values.rb +0 -18
- data/lib/aeternitas/sidekiq/middleware.rb +0 -31
- data/lib/aeternitas/sidekiq/poll_job.rb +0 -30
- data/lib/aeternitas/sidekiq.rb +0 -5
- data/logo.png +0 -0
- data/logo.svg +0 -198
data/lib/aeternitas/errors.rb
CHANGED
@@ -14,7 +14,6 @@ module Aeternitas
|
|
14
14
|
@original_error = original_error
|
15
15
|
super("#{original_error.class} - #{original_error.message}")
|
16
16
|
end
|
17
|
-
|
18
17
|
end
|
19
18
|
|
20
19
|
# Raised when a source data already exists.
|
@@ -45,4 +44,4 @@ module Aeternitas
|
|
45
44
|
end
|
46
45
|
end
|
47
46
|
end
|
48
|
-
end
|
47
|
+
end
|
data/lib/aeternitas/guard.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "active_support/duration"
|
2
|
+
require "securerandom"
|
3
3
|
|
4
4
|
module Aeternitas
|
5
5
|
# A distributed lock that can not be acquired after being unlocked for a certain time (cooldown period).
|
6
|
-
# Using
|
6
|
+
# Using a database table (`aeternitas_guard_locks`) with pessimistic locking we ensure atomicity and prevent race conditions.
|
7
7
|
#
|
8
8
|
# @example
|
9
9
|
# guard = Aeternitas::Guard.new("Twitter-MY_API_KEY", 5.seconds)
|
@@ -13,7 +13,7 @@ module Aeternitas
|
|
13
13
|
# end
|
14
14
|
# rescue Twitter::TooManyRequests => e
|
15
15
|
# guard.sleep_until(e.rate_limit.reset_at)
|
16
|
-
# raise Aeternitas::Guard::GuardIsLocked(e.rate_limit.reset_at)
|
16
|
+
# raise Aeternitas::Guard::GuardIsLocked.new(e.rate_limit.reset_at)
|
17
17
|
# end
|
18
18
|
#
|
19
19
|
# @!attribute [r] id
|
@@ -25,7 +25,6 @@ module Aeternitas
|
|
25
25
|
# @!attribute [r] token
|
26
26
|
# @return [String] cryptographic token which ensures we do not lock/unlock a guard held by another process
|
27
27
|
class Guard
|
28
|
-
|
29
28
|
attr_reader :id, :timeout, :cooldown, :token
|
30
29
|
|
31
30
|
# Create a new Guard
|
@@ -35,15 +34,15 @@ module Aeternitas
|
|
35
34
|
# @param [ActiveRecord::Duration] timeout Lock timeout
|
36
35
|
# @return [Aeternitas::Guard] Creates a new Instance
|
37
36
|
def initialize(id, cooldown, timeout = 10.minutes)
|
38
|
-
@id
|
39
|
-
@cooldown = cooldown
|
40
|
-
@timeout
|
41
|
-
@token
|
37
|
+
@id = id
|
38
|
+
@cooldown = Aeternitas.test_mode? ? 0.seconds : cooldown
|
39
|
+
@timeout = timeout
|
40
|
+
@token = SecureRandom.hex(10)
|
42
41
|
end
|
43
42
|
|
44
43
|
# Runs a given block if the lock can be acquired and releases the lock afterwards.
|
45
44
|
#
|
46
|
-
# @raise [Aeternitas::
|
45
|
+
# @raise [Aeternitas::Guard::GuardIsLocked] if the lock can not be acquired
|
47
46
|
# @example
|
48
47
|
# Guard.new("MyId", 5.seconds, 10.minutes).with_lock { do_request() }
|
49
48
|
def with_lock
|
@@ -55,6 +54,22 @@ module Aeternitas
|
|
55
54
|
end
|
56
55
|
end
|
57
56
|
|
57
|
+
# Tries to unlock the guard and starts the cooldown phase.
|
58
|
+
# It only releases the lock if the token matches and the state is 'processing'.
|
59
|
+
def unlock
|
60
|
+
Aeternitas::GuardLock.transaction do
|
61
|
+
lock = Aeternitas::GuardLock.where(lock_key: @id, token: @token).lock.first
|
62
|
+
return false unless lock&.processing?
|
63
|
+
|
64
|
+
lock.update!(
|
65
|
+
state: :cooldown,
|
66
|
+
locked_until: @cooldown.from_now,
|
67
|
+
reason: nil
|
68
|
+
)
|
69
|
+
end
|
70
|
+
true
|
71
|
+
end
|
72
|
+
|
58
73
|
# Locks the guard until the given time.
|
59
74
|
#
|
60
75
|
# @param [Time] until_time sleep time
|
@@ -68,118 +83,70 @@ module Aeternitas
|
|
68
83
|
# @param [ActiveSupport::Duration] duration sleeping duration
|
69
84
|
# @param [String] msg hint why the guard sleeps
|
70
85
|
def sleep_for(duration, msg = nil)
|
71
|
-
raise ArgumentError,
|
86
|
+
raise ArgumentError, "duration must be an ActiveRecord::Duration" unless duration.is_a?(ActiveSupport::Duration)
|
72
87
|
sleep_until(duration.from_now, msg)
|
73
88
|
end
|
74
89
|
|
75
90
|
private
|
76
91
|
|
77
92
|
# Tries to acquire the lock.
|
78
|
-
#
|
79
|
-
# @example The Redis value looks like this
|
80
|
-
# {
|
81
|
-
# id: 'MyId'
|
82
|
-
# state: 'processing'
|
83
|
-
# timeout: '3600'
|
84
|
-
# cooldown: '5'
|
85
|
-
# locked_until: '2017-01-01 10:10:00'
|
86
|
-
# token: '1234567890'
|
87
|
-
# }
|
88
93
|
# @raise [Aeternitas::Guard::GuardIsLocked] if the lock can not be acquired
|
89
94
|
def acquire_lock!
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
'token' => @token
|
125
|
-
}
|
126
|
-
|
127
|
-
Aeternitas.redis.set(@id, JSON.unparse(payload), ex: @cooldown.to_i)
|
95
|
+
retries = 0
|
96
|
+
begin
|
97
|
+
Aeternitas::GuardLock.transaction do
|
98
|
+
lock = Aeternitas::GuardLock.where(lock_key: @id).lock.first
|
99
|
+
|
100
|
+
if lock
|
101
|
+
if lock.locked_until > Time.current
|
102
|
+
# Lock is still active
|
103
|
+
raise GuardIsLocked.new(@id, lock.locked_until, lock.reason)
|
104
|
+
else
|
105
|
+
# Lock has expired
|
106
|
+
lock.update!(
|
107
|
+
token: @token,
|
108
|
+
state: :processing,
|
109
|
+
locked_until: @timeout.from_now,
|
110
|
+
reason: nil
|
111
|
+
)
|
112
|
+
end
|
113
|
+
else
|
114
|
+
# Create new lock
|
115
|
+
Aeternitas::GuardLock.create!(
|
116
|
+
lock_key: @id,
|
117
|
+
token: @token,
|
118
|
+
state: :processing,
|
119
|
+
locked_until: @timeout.from_now
|
120
|
+
)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
rescue ActiveRecord::RecordNotUnique
|
124
|
+
# prevent infinite loops in unexpected scenarios
|
125
|
+
retries += 1
|
126
|
+
raise if retries > 4
|
127
|
+
retry
|
128
|
+
end
|
128
129
|
end
|
129
130
|
|
130
|
-
# Lets the guard sleep until the given
|
131
|
-
# This
|
132
|
-
#
|
133
|
-
# @example The Redis value looks like this
|
134
|
-
# {
|
135
|
-
# id: 'MyId'
|
136
|
-
# state: 'sleeping'
|
137
|
-
# timeout: '3600'
|
138
|
-
# cooldown: '5'
|
139
|
-
# locked_until: '2017-01-01 13:00'
|
140
|
-
# message: "API Quota Reached"
|
141
|
-
# }
|
131
|
+
# Lets the guard sleep until the given time.
|
132
|
+
# This will create a new sleeping lock or update an existing one.
|
142
133
|
# @todo Should this raise an error if the lock is not owned by this instance?
|
143
134
|
# @param [Time] sleep_timeout for how long will the guard sleep
|
144
135
|
# @param [String] msg hint why the guard sleeps
|
145
136
|
def sleep(sleep_timeout, msg = nil)
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
# comparing the token value. If they match, than the lock is held by this instance.
|
160
|
-
#
|
161
|
-
# @todo Make the check atomic
|
162
|
-
# @return [Boolean] if the lock is held by this instance
|
163
|
-
def holds_lock?
|
164
|
-
payload = get_payload
|
165
|
-
payload['token'] == @token && payload['state'] == 'processing'
|
166
|
-
end
|
167
|
-
|
168
|
-
# Returns the guards current timeout.
|
169
|
-
#
|
170
|
-
# @return [Time] the guards current timeout
|
171
|
-
def get_timeout
|
172
|
-
payload = get_payload
|
173
|
-
payload['state'] == 'processing' ? payload['cooldown'].to_i.seconds.from_now : Time.parse(payload['locked_until'])
|
174
|
-
end
|
175
|
-
|
176
|
-
# Retrieves the locks payload from redis.
|
177
|
-
#
|
178
|
-
# @return [Hash] the locks payload
|
179
|
-
def get_payload
|
180
|
-
value = Aeternitas.redis.get(@id)
|
181
|
-
return {} unless value
|
182
|
-
JSON.parse(value)
|
137
|
+
sleep_timeout = Time.now if Aeternitas.test_mode?
|
138
|
+
Aeternitas::GuardLock.transaction do
|
139
|
+
lock = Aeternitas::GuardLock.where(lock_key: @id).lock.first_or_initialize
|
140
|
+
|
141
|
+
lock.assign_attributes(
|
142
|
+
token: @token,
|
143
|
+
state: :sleeping,
|
144
|
+
locked_until: sleep_timeout,
|
145
|
+
reason: msg
|
146
|
+
)
|
147
|
+
|
148
|
+
lock.save!
|
149
|
+
end
|
183
150
|
end
|
184
151
|
|
185
152
|
# Custom error class thrown when the lock can not be acquired
|
@@ -196,4 +163,4 @@ module Aeternitas
|
|
196
163
|
end
|
197
164
|
end
|
198
165
|
end
|
199
|
-
end
|
166
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "active_record"
|
2
|
+
|
3
|
+
module Aeternitas
|
4
|
+
# Stores the state of a distributed lock in the database.
|
5
|
+
class GuardLock < ActiveRecord::Base
|
6
|
+
self.table_name = "aeternitas_guard_locks"
|
7
|
+
|
8
|
+
enum :state, {
|
9
|
+
processing: "processing",
|
10
|
+
cooldown: "cooldown",
|
11
|
+
sleeping: "sleeping"
|
12
|
+
}
|
13
|
+
|
14
|
+
validates :lock_key, presence: true, uniqueness: true
|
15
|
+
validates :locked_until, presence: true
|
16
|
+
validates :token, presence: true
|
17
|
+
validates :state, presence: true, inclusion: {in: states.keys}
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Aeternitas
|
2
|
+
# Provides methods for cleaning up Aeternitas data.
|
3
|
+
module Maintenance
|
4
|
+
def self.cleanup_all
|
5
|
+
cleanup_stale_locks
|
6
|
+
cleanup_old_metrics
|
7
|
+
end
|
8
|
+
|
9
|
+
# Clean up stale job and guard locks that have passed their expiration.
|
10
|
+
def self.cleanup_stale_locks
|
11
|
+
logger = ActiveJob::Base.logger
|
12
|
+
logger.info "Cleaning up stale Aeternitas locks..."
|
13
|
+
|
14
|
+
unique_job_locks_deleted = Aeternitas::UniqueJobLock.where("expires_at < ?", Time.current).delete_all
|
15
|
+
guard_locks_deleted = Aeternitas::GuardLock.where("locked_until < ?", Time.current).delete_all
|
16
|
+
|
17
|
+
logger.info " - Deleted #{unique_job_locks_deleted} stale unique job locks."
|
18
|
+
logger.info " - Deleted #{guard_locks_deleted} stale guard locks."
|
19
|
+
logger.info "Stale lock cleanup complete."
|
20
|
+
end
|
21
|
+
|
22
|
+
# Clean up old metric records to prevent the table from growing too large.
|
23
|
+
def self.cleanup_old_metrics
|
24
|
+
logger = ActiveJob::Base.logger
|
25
|
+
cutoff_date = Aeternitas.config.metric_retention_period.ago
|
26
|
+
|
27
|
+
logger.info "Cleaning up Aeternitas metrics older than #{cutoff_date}..."
|
28
|
+
|
29
|
+
metrics_deleted = Aeternitas::Metric.where("created_at < ?", cutoff_date).delete_all
|
30
|
+
|
31
|
+
logger.info " - Deleted #{metrics_deleted} old metric records."
|
32
|
+
logger.info "Old metrics cleanup complete."
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "active_record"
|
2
|
+
|
3
|
+
module Aeternitas
|
4
|
+
# Stores a single metric data point in the database.
|
5
|
+
class Metric < ActiveRecord::Base
|
6
|
+
self.table_name = "aeternitas_metrics"
|
7
|
+
|
8
|
+
validates :name, presence: true
|
9
|
+
validates :pollable_class, presence: true
|
10
|
+
validates :value, presence: true
|
11
|
+
end
|
12
|
+
end
|
data/lib/aeternitas/metrics.rb
CHANGED
@@ -1,18 +1,14 @@
|
|
1
|
-
require 'aeternitas/metrics/ten_minutes_resolution'
|
2
|
-
require 'aeternitas/metrics/counter'
|
3
|
-
require 'aeternitas/metrics/values'
|
4
|
-
require 'aeternitas/metrics/ratio'
|
5
|
-
|
6
1
|
module Aeternitas
|
7
|
-
# Provides
|
8
|
-
# Every metric is scoped by pollable class.
|
2
|
+
# Provides a simplified metrics system for Aeternitas.
|
3
|
+
# Every metric is scoped by pollable class and logged in the `aeternitas_metrics` table.
|
4
|
+
#
|
9
5
|
# Available metrics are:
|
10
6
|
# - polls => Number of polling runs
|
11
7
|
# - successful_polls => Number of successful polling runs
|
12
8
|
# - failed_polls => Number of failed polling runs (includes IgnoredErrors,
|
13
9
|
# excludes deactivation errors and Lock errors)
|
14
10
|
# - ignored_errors => Number of raised {Aeternitas::Errors::Ignored}
|
15
|
-
# -
|
11
|
+
# - deactivations => Number of deactivations
|
16
12
|
# - execution_time => Job execution time in seconds
|
17
13
|
# - guard_locked => Number of encountered locked guards
|
18
14
|
# - guard_timeout => Time until the guard is unlocked in seconds
|
@@ -20,143 +16,65 @@ module Aeternitas
|
|
20
16
|
# - pollables_created => Number of created pollables
|
21
17
|
# - sources_created => Number of created sources
|
22
18
|
#
|
23
|
-
# Available Resolutions are:
|
24
|
-
# - :minute (stored for 3 days)
|
25
|
-
# - :ten_minutes (stored for 14 days)
|
26
|
-
# - :hour (stored for 2 months)
|
27
|
-
# - :day (stored indefinitely)
|
28
|
-
#
|
29
|
-
# Every metric can be accessed via a getter method:
|
30
19
|
# @example
|
31
|
-
# Aeternitas::Metrics.polls MyPollable
|
32
|
-
# Aeternitas::Metrics.
|
33
|
-
#
|
20
|
+
# Aeternitas::Metrics.log(:polls, MyPollable)
|
21
|
+
# Aeternitas::Metrics.log_value(:execution_time, MyPollable, 1.25)
|
22
|
+
#
|
23
|
+
# # Get all poll counts for MyPollable in the last day
|
24
|
+
# Aeternitas::Metrics.get(:polls, MyPollable, from: 1.day.ago)
|
25
|
+
#
|
34
26
|
module Metrics
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
TabsTabs.configure do |tabstabs_config|
|
50
|
-
tabstabs_config.unregister_resolutions(:week, :month, :year)
|
51
|
-
|
52
|
-
tabstabs_config.register_resolution Aeternitas::Metrics::TenMinutesResolution
|
53
|
-
|
54
|
-
tabstabs_config.set_expirations(
|
55
|
-
minute: 3.days,
|
56
|
-
ten_minutes: 14.days,
|
57
|
-
hour: 2.months
|
58
|
-
)
|
59
|
-
end
|
27
|
+
# A list of all available metric names.
|
28
|
+
AVAILABLE_METRICS = [
|
29
|
+
:polls,
|
30
|
+
:successful_polls,
|
31
|
+
:failed_polls,
|
32
|
+
:ignored_errors,
|
33
|
+
:deactivations,
|
34
|
+
:execution_time,
|
35
|
+
:guard_locked,
|
36
|
+
:guard_timeout,
|
37
|
+
:guard_timeout_exceeded,
|
38
|
+
:sources_created,
|
39
|
+
:pollables_created
|
40
|
+
].freeze
|
60
41
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
self.get(:#{metric}, pollable, from: from, to: to, resolution: resolution )
|
65
|
-
end
|
66
|
-
eoruby
|
67
|
-
end
|
68
|
-
|
69
|
-
# Increses the specified counter metric for the given pollable.
|
70
|
-
# @param [Symbol, String] name the metric
|
71
|
-
# @param [Pollable] pollable_class pollable instance
|
42
|
+
# Logs a counter metric. This creates a new metric record with a value of 1.
|
43
|
+
# @param [Symbol] name the metric name
|
44
|
+
# @param [Class] pollable_class the class of the pollable
|
72
45
|
def self.log(name, pollable_class)
|
73
|
-
|
74
|
-
raise ArgumentError, "#{name} isn't a Counter" unless AVAILABLE_METRICS[name] == :counter
|
75
|
-
begin
|
76
|
-
TabsTabs.increment_counter(get_key(name, pollable_class))
|
77
|
-
TabsTabs.increment_counter(get_key(name, Aeternitas::Pollable))
|
78
|
-
rescue StandardError ; end
|
46
|
+
log_value(name, pollable_class, 1)
|
79
47
|
end
|
80
48
|
|
81
|
-
# Logs a value
|
82
|
-
# @param [Symbol
|
83
|
-
# @param [
|
84
|
-
# @param [
|
49
|
+
# Logs a value-based metric.
|
50
|
+
# @param [Symbol] name the metric name
|
51
|
+
# @param [Class] pollable_class the class of the pollable
|
52
|
+
# @param [Float] value the value to log
|
85
53
|
def self.log_value(name, pollable_class, value)
|
86
|
-
|
87
|
-
raise(ArgumentError, "#{name} isn't a Value") unless AVAILABLE_METRICS[name] == :value
|
88
|
-
begin
|
89
|
-
TabsTabs.record_value(get_key(name, pollable_class), value)
|
90
|
-
TabsTabs.record_value(get_key(name, Aeternitas::Pollable), value)
|
91
|
-
rescue StandardError ; end
|
92
|
-
end
|
93
|
-
|
94
|
-
# Retrieves the stats of the given metric in the given time frame and resolution.
|
95
|
-
# @param [Symbol String] name the metric
|
96
|
-
# @param [Object] pollable_class the pollable class
|
97
|
-
# @param [DateTime] from begin of the time frame
|
98
|
-
# @param [DateTime] to end of the timeframe
|
99
|
-
# @param [Symbol] resolution resolution
|
100
|
-
# @return [Aeternitas::Metrics::Counter, Aeternitas::Metrics::Value] stats
|
101
|
-
def self.get(name, pollable_class, from: 1.hour.ago, to: Time.now, resolution: :minute)
|
102
|
-
raise('Metric not found') unless AVAILABLE_METRICS.key? name
|
103
|
-
raise('Invalid interval') if from > to
|
104
|
-
result = TabsTabs.get_stats(get_key(name, pollable_class), from..to, resolution)
|
105
|
-
if AVAILABLE_METRICS[name] == :counter
|
106
|
-
Counter.new(result)
|
107
|
-
else
|
108
|
-
Values.new(result)
|
109
|
-
end
|
110
|
-
rescue TabsTabs::UnknownMetricError => _
|
111
|
-
TabsTabs.create_metric(get_key(name, pollable_class), AVAILABLE_METRICS[name].to_s)
|
112
|
-
get(name, pollable_class, from: from, to: to, resolution: resolution)
|
113
|
-
end
|
54
|
+
return unless Aeternitas.config.metrics_enabled && AVAILABLE_METRICS.include?(name)
|
114
55
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
polls = polls(pollable_class, from: from, to: to, resolution: resolution)
|
124
|
-
failed_polls = failed_polls(pollable_class, from: from, to: to, resolution: resolution)
|
125
|
-
Ratio.new(from, to, resolution, calculate_ratio(polls, failed_polls))
|
126
|
-
end
|
127
|
-
|
128
|
-
# Returns the lock ratio of the given job for given time frame and resolution
|
129
|
-
# @param [Symbol String] name the metric
|
130
|
-
# @param [Object] pollable_class the pollable class
|
131
|
-
# @param [DateTime] from begin of the time frame
|
132
|
-
# @param [DateTime] to end of the timeframe
|
133
|
-
# @param [Symbol] resolution resolution
|
134
|
-
# @return [Aeternitas::Metrics::Ratio] ratio time series
|
135
|
-
def self.guard_locked_ratio(pollable_class, from: 1.hour.ago, to: Time.now, resolution: :minute)
|
136
|
-
polls = polls(pollable_class, from: from, to: to, resolution: resolution)
|
137
|
-
guard_locked = guard_locked(pollable_class, from: from, to: to, resolution: resolution)
|
138
|
-
Ratio.new(from, to, resolution, calculate_ratio(polls, guard_locked))
|
139
|
-
end
|
140
|
-
|
141
|
-
# Computes the metric key of a given metric-pollable pair
|
142
|
-
# @param [Symbol, String] name the metric
|
143
|
-
# @param [Object] pollable_class pollable class
|
144
|
-
# @return [String] the metric key
|
145
|
-
def self.get_key(name, pollable_class)
|
146
|
-
"#{name}:#{pollable_class.name}"
|
56
|
+
Aeternitas::Metric.create(
|
57
|
+
name: name.to_s,
|
58
|
+
pollable_class: pollable_class.name,
|
59
|
+
value: value,
|
60
|
+
created_at: Time.now
|
61
|
+
)
|
62
|
+
rescue
|
63
|
+
# Metrics should fail silently
|
147
64
|
end
|
148
65
|
|
149
|
-
#
|
150
|
-
# @param [
|
151
|
-
# @param [
|
152
|
-
# @
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
66
|
+
# Retrieves metric records.
|
67
|
+
# @param [Symbol] name the metric
|
68
|
+
# @param [Class] pollable_class the pollable class
|
69
|
+
# @param [Time] from begin of the time frame
|
70
|
+
# @param [Time] to end of the timeframe
|
71
|
+
# @return [ActiveRecord::Relation] a relation of Aeternitas::Metric records
|
72
|
+
def self.get(name, pollable_class, from: 1.hour.ago, to: Time.now)
|
73
|
+
Aeternitas::Metric.where(
|
74
|
+
name: name.to_s,
|
75
|
+
pollable_class: pollable_class.name,
|
76
|
+
created_at: from..to
|
77
|
+
).order(:created_at)
|
160
78
|
end
|
161
79
|
end
|
162
|
-
end
|
80
|
+
end
|