ecoportal-api 0.10.5 → 0.10.6
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/CHANGELOG.md +21 -1
- data/lib/ecoportal/api/errors/time_out.rb +3 -0
- data/lib/ecoportal/api/v1/job/awaiter/status_frequency.rb +49 -0
- data/lib/ecoportal/api/v1/job/awaiter/timer.rb +158 -0
- data/lib/ecoportal/api/v1/job/awaiter.rb +41 -70
- data/lib/ecoportal/api/v1/job/status.rb +15 -1
- data/lib/ecoportal/api/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ae05088d3d0542b2200a614b0043d8580cd713cbfcf4fb473b17be4f1930aafe
|
4
|
+
data.tar.gz: 90259cfa1c993c457b2bcfcd57d343538c7bf4f3746b21c921eb1be3e92dc370
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '058bd304110936dc08ee37c77c081b65fc08b68a29f60ff74ead0798e0894100a26f6a85b60ce4d38c447d74707f78eec55a69755002ec10c6b36b98f4db8b67'
|
7
|
+
data.tar.gz: 7340fe5e7875a9a2b3aefda197927fbfe733299c0dc0494b3b09b59db24ae7bac80b135ea0fb9fd1d7b5abc1ed246a715e3f3b14ee5d26e45a8bef2f14017815
|
data/CHANGELOG.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
4
4
|
|
5
|
-
## [0.10.
|
5
|
+
## [0.10.7] - 2024-10-xx
|
6
6
|
|
7
7
|
### Added
|
8
8
|
|
@@ -10,6 +10,26 @@ All notable changes to this project will be documented in this file.
|
|
10
10
|
|
11
11
|
### Fixed
|
12
12
|
|
13
|
+
## [0.10.6] - 2024-10-28
|
14
|
+
|
15
|
+
### Added
|
16
|
+
|
17
|
+
- `Ecoportal::API::V1::Job::Status`
|
18
|
+
- **added** methods `started?`, `progressing?` and `progress_increase`
|
19
|
+
- `Ecoportal::API::Errors::StartTimeOut` specificity on timeout error.
|
20
|
+
|
21
|
+
### Changed
|
22
|
+
|
23
|
+
- Refactored `Ecoportal::API::V1::Awaiter` into:
|
24
|
+
- `Awaiter::Timer`: account for last waited, and manage time outs
|
25
|
+
- `Awaiter::StatusFrequency`: next awaiting time
|
26
|
+
|
27
|
+
### Fixed
|
28
|
+
|
29
|
+
- `Portal::API::V1::Awaiter`
|
30
|
+
- `throughput` based on progress during last wait
|
31
|
+
- this helps to better detect non progressing jobs
|
32
|
+
|
13
33
|
## [0.10.5] - 2024-10-16
|
14
34
|
|
15
35
|
### Fixed
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Ecoportal
|
2
|
+
module API
|
3
|
+
class V1
|
4
|
+
class Job
|
5
|
+
class Awaiter
|
6
|
+
module StatusFrequency
|
7
|
+
include Common::Client::TimeOut
|
8
|
+
|
9
|
+
DELAY_STATUS_CHECK = 4
|
10
|
+
MIN_STATUS_CHECK = 2
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def status_checked?
|
15
|
+
@status_checked || false
|
16
|
+
end
|
17
|
+
|
18
|
+
def status_check_in(pending, timeout_in:)
|
19
|
+
# first check to be faster
|
20
|
+
unless status_checked?
|
21
|
+
@status_checked = true unless pending.zero? # <= increases job status requests!
|
22
|
+
return min_delay_status_check
|
23
|
+
end
|
24
|
+
|
25
|
+
return default_delay_status_check if around_min_throughput?
|
26
|
+
|
27
|
+
eta = eta_for(pending, approach: :optimistic)
|
28
|
+
check_in_max = [eta, timeout_in].min * 0.90
|
29
|
+
|
30
|
+
check_in_best = check_in_max / 2.0
|
31
|
+
default_5 = default_delay_status_check * 5
|
32
|
+
|
33
|
+
top_check = [default_5, check_in_best].min.ceil
|
34
|
+
[top_check, min_delay_status_check].max
|
35
|
+
end
|
36
|
+
|
37
|
+
def default_delay_status_check
|
38
|
+
self.class::DELAY_STATUS_CHECK
|
39
|
+
end
|
40
|
+
|
41
|
+
def min_delay_status_check
|
42
|
+
self.class::MIN_STATUS_CHECK
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
module Ecoportal
|
2
|
+
module API
|
3
|
+
class V1
|
4
|
+
class Job
|
5
|
+
class Awaiter
|
6
|
+
TIMER_ARGS = %i[
|
7
|
+
total timeout
|
8
|
+
start last ldelay
|
9
|
+
status lstatus
|
10
|
+
].freeze
|
11
|
+
|
12
|
+
Timer = Struct.new(*TIMER_ARGS) do
|
13
|
+
self::MAX_START_DELAY = 60
|
14
|
+
|
15
|
+
attr_reader :timestamp
|
16
|
+
|
17
|
+
def initialize(...)
|
18
|
+
@timestamp = Time.now
|
19
|
+
super
|
20
|
+
yield(self) if block_given?
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_method :original_start, :start
|
24
|
+
# `start` time only counts from the moment that
|
25
|
+
# it already started to progress
|
26
|
+
def start
|
27
|
+
return timestamp unless lstatus&.started?
|
28
|
+
|
29
|
+
original_start || timestamp
|
30
|
+
end
|
31
|
+
|
32
|
+
alias_method :original_last, :last
|
33
|
+
def last
|
34
|
+
original_last || start
|
35
|
+
end
|
36
|
+
|
37
|
+
def new(**kargs, &block)
|
38
|
+
self.class.new(**new_kargs.merge(kargs), &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def job_id
|
42
|
+
status.id
|
43
|
+
end
|
44
|
+
|
45
|
+
def complete?
|
46
|
+
status.complete?(total)
|
47
|
+
end
|
48
|
+
|
49
|
+
def started?
|
50
|
+
status.started?
|
51
|
+
end
|
52
|
+
|
53
|
+
def pending
|
54
|
+
status.pending(total)
|
55
|
+
end
|
56
|
+
|
57
|
+
def progress
|
58
|
+
status.progress
|
59
|
+
end
|
60
|
+
|
61
|
+
def increased
|
62
|
+
status.progress_increase(lstatus)
|
63
|
+
end
|
64
|
+
|
65
|
+
def waited
|
66
|
+
timestamp - start
|
67
|
+
end
|
68
|
+
|
69
|
+
def lwaited
|
70
|
+
timestamp - last
|
71
|
+
end
|
72
|
+
|
73
|
+
def net_waited
|
74
|
+
last_delay = ldelay || 0
|
75
|
+
waited - (last_delay / 2)
|
76
|
+
end
|
77
|
+
|
78
|
+
def time_left
|
79
|
+
(timeout - waited).round(2)
|
80
|
+
end
|
81
|
+
|
82
|
+
def time_left_to_start
|
83
|
+
return max_start_delay if started?
|
84
|
+
|
85
|
+
(max_start_delay - waited).round(2)
|
86
|
+
end
|
87
|
+
|
88
|
+
def timeout_in
|
89
|
+
return time_left if started?
|
90
|
+
|
91
|
+
time_left_to_start
|
92
|
+
end
|
93
|
+
|
94
|
+
# timeout library is evil. So we make poor-man timeout.
|
95
|
+
# https://jvns.ca/blog/2015/11/27/why-rubys-timeout-is-dangerous-and-thread-dot-raise-is-terrifying/
|
96
|
+
def timeout?
|
97
|
+
!time_left.positive?
|
98
|
+
end
|
99
|
+
|
100
|
+
def start_timeout?
|
101
|
+
return false if status.started?
|
102
|
+
|
103
|
+
!time_left_to_start.positive?
|
104
|
+
end
|
105
|
+
|
106
|
+
def on_timeout!(&block)
|
107
|
+
return start_timeout!(&block) if start_timeout?
|
108
|
+
return timeout!(&block) if timeout?
|
109
|
+
|
110
|
+
false
|
111
|
+
end
|
112
|
+
|
113
|
+
def timeout!
|
114
|
+
return false unless timeout?
|
115
|
+
|
116
|
+
yield(self)
|
117
|
+
|
118
|
+
msg = "Job '#{job_id}' not complete (size: #{total}).\n"
|
119
|
+
msg << " Timed out after #{timeout} seconds.\n"
|
120
|
+
msg << " Current status: #{status}"
|
121
|
+
|
122
|
+
raise API::Errors::TimeOut, msg
|
123
|
+
end
|
124
|
+
|
125
|
+
def start_timeout!
|
126
|
+
return false unless start_timeout?
|
127
|
+
|
128
|
+
yield(self)
|
129
|
+
|
130
|
+
msg = "Job '#{job_id}' not started (size: #{total}).\n"
|
131
|
+
msg << " Start timed out after #{max_start_delay} seconds.\n"
|
132
|
+
msg << " Current status: #{status}"
|
133
|
+
|
134
|
+
raise API::Errors::StartTimeOut, msg
|
135
|
+
end
|
136
|
+
|
137
|
+
protected
|
138
|
+
|
139
|
+
def new_kargs
|
140
|
+
%i[total timeout start].each_with_object({}) do |key, kargs|
|
141
|
+
kargs[key] = send(key)
|
142
|
+
end.tap do |kargs|
|
143
|
+
kargs[:start] = timestamp
|
144
|
+
kargs[:lstatus] = status
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def max_start_delay
|
151
|
+
self.class::MAX_START_DELAY
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -1,64 +1,77 @@
|
|
1
|
+
require_relative 'awaiter/timer'
|
2
|
+
require_relative 'awaiter/status_frequency'
|
3
|
+
|
1
4
|
module Ecoportal
|
2
5
|
module API
|
3
6
|
class V1
|
4
7
|
class Job
|
5
8
|
class Awaiter
|
6
9
|
include Common::Client::TimeOut
|
7
|
-
|
8
|
-
DELAY_STATUS_CHECK = 4
|
9
|
-
MIN_STATUS_CHECK = 2
|
10
|
+
include StatusFrequency
|
10
11
|
|
11
12
|
TIMEOUT_APPROACH = :min # :conservative # adaptative timeout
|
12
13
|
TIMEOUT_FALLBACK = :min
|
13
14
|
|
14
|
-
attr_reader :
|
15
|
-
attr_accessor :timeout_approach
|
15
|
+
attr_reader :job_api, :job_id, :total
|
16
16
|
|
17
|
-
def initialize(
|
18
|
-
@
|
17
|
+
def initialize(job_api, job_id:, total:)
|
18
|
+
@job_api = job_api
|
19
19
|
@job_id = job_id
|
20
20
|
@total = total
|
21
|
-
|
22
|
-
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_writer :timeout_approach
|
24
|
+
|
25
|
+
def timeout_approach
|
26
|
+
@timeout_approach ||= self.class::TIMEOUT_APPROACH
|
23
27
|
end
|
24
28
|
|
25
29
|
# Allows to preserve the learned throughput
|
26
30
|
def new(**kargs)
|
27
|
-
self.class.new(
|
28
|
-
out.throughput
|
31
|
+
self.class.new(job_api, **kargs).tap do |out|
|
32
|
+
out.throughput = throughput
|
33
|
+
out.timeout_approach = timeout_approach
|
29
34
|
end
|
30
35
|
end
|
31
36
|
|
32
37
|
def await_completion! # rubocop:disable Metrics/AbcSize
|
33
|
-
|
38
|
+
timeout = timeout_for(total, approach: timeout_approach)
|
39
|
+
|
40
|
+
first = 1
|
41
|
+
timer = Timer.new(
|
42
|
+
total: total,
|
43
|
+
timeout: timeout,
|
44
|
+
ldelay: first
|
45
|
+
)
|
34
46
|
|
35
|
-
# timeout library is evil. So we make poor-man timeout.
|
36
|
-
# https://jvns.ca/blog/2015/11/27/why-rubys-timeout-is-dangerous-and-thread-dot-raise-is-terrifying/
|
37
|
-
before = Time.now
|
38
47
|
delay_status_check = nil
|
39
48
|
|
40
49
|
loop do
|
41
|
-
|
42
|
-
done = status.progress
|
43
|
-
waited = Time.now - before
|
50
|
+
sleep(first.tap {first = nil}) if first
|
44
51
|
|
45
|
-
|
46
|
-
|
47
|
-
|
52
|
+
timer = timer.new(
|
53
|
+
status: job_api.status(job_id),
|
54
|
+
ldelay: delay_status_check
|
55
|
+
)
|
48
56
|
|
49
|
-
|
57
|
+
# ratio = throughput!(timer.net_waited, count: timer.progress)
|
58
|
+
ratio = throughput!(timer.lwaited, count: timer.increased)
|
50
59
|
|
51
|
-
|
52
|
-
left = max_timeout - waited
|
60
|
+
break timer.status if timer.complete?
|
53
61
|
|
54
|
-
|
62
|
+
timer.on_timeout! do
|
63
|
+
@timeout_approach = self.class::TIMEOUT_FALLBACK
|
64
|
+
end
|
55
65
|
|
56
|
-
delay_status_check = status_check_in(
|
66
|
+
delay_status_check = status_check_in(
|
67
|
+
timer.pending,
|
68
|
+
timeout_in: timer.timeout_in
|
69
|
+
)
|
57
70
|
|
58
71
|
msg = " ... Awaiting #{delay_status_check} s. -- "
|
59
|
-
msg << " TimeOut: #{
|
72
|
+
msg << " TimeOut: #{timer.time_left} s. "
|
60
73
|
msg << "(job '#{job_id}') "
|
61
|
-
msg << "Done: #{
|
74
|
+
msg << "Done: #{timer.progress} (est. #{ratio.round(2)} rec/s) "
|
62
75
|
msg << " \r"
|
63
76
|
|
64
77
|
print msg
|
@@ -67,48 +80,6 @@ module Ecoportal
|
|
67
80
|
sleep(delay_status_check)
|
68
81
|
end
|
69
82
|
end
|
70
|
-
|
71
|
-
private
|
72
|
-
|
73
|
-
def timeout!(status, timeout:)
|
74
|
-
self.timeout_approach = self.class::TIMEOUT_FALLBACK
|
75
|
-
|
76
|
-
msg = "Job '#{job_id}' not complete (size: #{total}).\n"
|
77
|
-
msg << " Timed out after #{timeout} seconds.\n"
|
78
|
-
msg << " Current status: #{status}"
|
79
|
-
|
80
|
-
raise API::Errors::TimeOut, msg
|
81
|
-
end
|
82
|
-
|
83
|
-
def checked?
|
84
|
-
@checked
|
85
|
-
end
|
86
|
-
|
87
|
-
def status_check_in(pending, timeout_in:)
|
88
|
-
unless checked?
|
89
|
-
@checked = true
|
90
|
-
return min_delay_status_check
|
91
|
-
end
|
92
|
-
|
93
|
-
return default_delay_status_check if around_min_throughput?
|
94
|
-
|
95
|
-
eta = eta_for(pending, approach: :optimistic)
|
96
|
-
check_in_max = [eta, timeout_in].min * 0.90
|
97
|
-
|
98
|
-
check_in_best = check_in_max / 2.0
|
99
|
-
default_5 = default_delay_status_check * 5
|
100
|
-
|
101
|
-
top_check = [default_5, check_in_best].min.ceil
|
102
|
-
[top_check, min_delay_status_check].max
|
103
|
-
end
|
104
|
-
|
105
|
-
def default_delay_status_check
|
106
|
-
self.class::DELAY_STATUS_CHECK
|
107
|
-
end
|
108
|
-
|
109
|
-
def min_delay_status_check
|
110
|
-
self.class::MIN_STATUS_CHECK
|
111
|
-
end
|
112
83
|
end
|
113
84
|
end
|
114
85
|
end
|
@@ -9,7 +9,15 @@ module Ecoportal
|
|
9
9
|
@id = id
|
10
10
|
@complete = complete
|
11
11
|
@errored = errored
|
12
|
-
@progress = progress
|
12
|
+
@progress = progress || 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def started?
|
16
|
+
progress.positive?
|
17
|
+
end
|
18
|
+
|
19
|
+
def progressing?(prev)
|
20
|
+
progress_increase(prev).positive?
|
13
21
|
end
|
14
22
|
|
15
23
|
def complete?(total = nil)
|
@@ -25,6 +33,12 @@ module Ecoportal
|
|
25
33
|
total - progress
|
26
34
|
end
|
27
35
|
|
36
|
+
def progress_increase(prev = 0)
|
37
|
+
prev = prev.progress if prev.is_a?(self.class)
|
38
|
+
prev ||= 0
|
39
|
+
progress - prev
|
40
|
+
end
|
41
|
+
|
28
42
|
def errored?
|
29
43
|
@errored
|
30
44
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ecoportal-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.10.
|
4
|
+
version: 0.10.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tapio Saarinen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-10-
|
11
|
+
date: 2024-10-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pry
|
@@ -258,6 +258,8 @@ files:
|
|
258
258
|
- lib/ecoportal/api/v1.rb
|
259
259
|
- lib/ecoportal/api/v1/job.rb
|
260
260
|
- lib/ecoportal/api/v1/job/awaiter.rb
|
261
|
+
- lib/ecoportal/api/v1/job/awaiter/status_frequency.rb
|
262
|
+
- lib/ecoportal/api/v1/job/awaiter/timer.rb
|
261
263
|
- lib/ecoportal/api/v1/job/status.rb
|
262
264
|
- lib/ecoportal/api/v1/people.rb
|
263
265
|
- lib/ecoportal/api/v1/person.rb
|