tiny_rl 1.2.0 → 2.1.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/tiny_rl.rb +119 -2
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d9dfba7e07aff49aacdce244cf39bc54f87afb03ab40823ee5e7aa56863f80a
|
4
|
+
data.tar.gz: fb9a2e33e35858d05da57d5976ab2db87644b694c67ca35df8240b959f0e0580
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6b823cdb7a65a0e35a15ef298a383346809a4d443b6132b4221f21c025cdfbd797a95982fabe89a3761d8b601d7cdb120f011c38b783ad0cab09b22033fb8a8c
|
7
|
+
data.tar.gz: 80e43f6a8e144cdadf44f3a660960cbb7876c74259d07652dc838de1e8d32ca790684fa8809b1a44b734fa4ac5c69f0bdbaa49dc97acf71121b1ca8a952b152f
|
data/lib/tiny_rl.rb
CHANGED
@@ -1,2 +1,119 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
# this class simply tracks a rate limit, the timestamps of things you want to
|
4
|
+
# limit, and allows you the check both the used capacity and whether or not the
|
5
|
+
# rate limit has been reached. there's some additional tracking of the total
|
6
|
+
# number of jobs, dropped jobs, and errored jobs, just for some simple
|
7
|
+
# accounting.
|
8
|
+
#
|
9
|
+
# Usage:
|
10
|
+
# > rl = TinyRl.new(5, TinyRl::MINUTE, :drop)
|
11
|
+
# => <a rate limiter that will drop method calls over a rate of 5 per minute>
|
12
|
+
# > 10.times{ rl.track }
|
13
|
+
# > rl.used_capacity
|
14
|
+
# => 5
|
15
|
+
# > rl.at_capacity?
|
16
|
+
# => true
|
17
|
+
#
|
18
|
+
# if you use the :error strategy, then an instance of
|
19
|
+
# TinyRl::ExceededRateLimitError will be raised when you exceed the rate limit
|
20
|
+
# when calling #track.
|
21
|
+
#
|
22
|
+
# if you'd like to check the capacity of the TinyRl before taking some action,
|
23
|
+
# then check either TinyRl#at_capacity? or TinyRl#used_capacity as appropriate.
|
24
|
+
class TinyRl
|
25
|
+
STRATEGIES = %i(drop error)
|
26
|
+
|
27
|
+
SECOND = 1
|
28
|
+
MINUTE = SECOND * 60
|
29
|
+
HOUR = MINUTE * 60
|
30
|
+
DAY = HOUR * 24
|
31
|
+
WEEK = DAY * 7
|
32
|
+
MONTH = DAY * 30
|
33
|
+
YEAR = DAY * 365
|
34
|
+
|
35
|
+
attr_reader :limit,
|
36
|
+
:per,
|
37
|
+
:strategy
|
38
|
+
|
39
|
+
# rate: number of requests per unit time
|
40
|
+
# per: unit of time (one of SECOND, MINUTE, etc.)
|
41
|
+
# you can pass any integer in as the `per' parameter and it will be
|
42
|
+
# interpreted as a number of seconds
|
43
|
+
# strategy: what to do when you're over the rate limit
|
44
|
+
# drop: drop the request, never perform the method call
|
45
|
+
# error: raise an exception when rate exceeded
|
46
|
+
def initialize(limit, per, strategy)
|
47
|
+
raise TinyRlInvalidStrategyError.new("Strategy `#{strategy.inspect}' is not one of the allowed strategies") unless STRATEGIES.include?(strategy)
|
48
|
+
@total_jobs = 0
|
49
|
+
@dropped_jobs = 0
|
50
|
+
@errored_jobs = 0
|
51
|
+
@limit = limit
|
52
|
+
@per = per
|
53
|
+
@strategy = strategy
|
54
|
+
|
55
|
+
@job_call_times = []
|
56
|
+
end
|
57
|
+
|
58
|
+
# this method will track the timestamp of something that you want to rate
|
59
|
+
# limit. this is generic so that you cand rate limit a bunch of things
|
60
|
+
# collectively with a single TinyRl instance.
|
61
|
+
#
|
62
|
+
# for strategy :drop
|
63
|
+
# returns true if the job was run, false if it was dropped
|
64
|
+
# for strategy :error
|
65
|
+
# returns true if the job was run, raises an exception otherwise
|
66
|
+
def track
|
67
|
+
@total_jobs += 1
|
68
|
+
|
69
|
+
if at_capacity?
|
70
|
+
case @strategy
|
71
|
+
when :drop
|
72
|
+
@dropped_jobs += 1
|
73
|
+
when :error
|
74
|
+
@errored_jobs += 1
|
75
|
+
raise TinyRlExceededRateLimitError.new("Rate limit of #{@limit} per #{@per} sec exceeded")
|
76
|
+
end
|
77
|
+
false
|
78
|
+
else
|
79
|
+
@job_call_times << Time.now
|
80
|
+
true
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# returns true if the rate limit has currently been reached, false otherwise
|
85
|
+
def at_capacity?
|
86
|
+
_clear_old_jobs
|
87
|
+
@job_call_times.length == @limit
|
88
|
+
end
|
89
|
+
|
90
|
+
# useful for monitoring by consuming programs, and for issuing warnings when
|
91
|
+
# you're close, but not over, your limit
|
92
|
+
def used_capacity
|
93
|
+
_clear_old_jobs
|
94
|
+
@job_call_times.length
|
95
|
+
end
|
96
|
+
|
97
|
+
# removes old jobs before. used before checking capacity
|
98
|
+
def _clear_old_jobs
|
99
|
+
loop do
|
100
|
+
break if (@job_call_times.length == 0) || (@job_call_times.first >= (Time.now - @per))
|
101
|
+
@job_call_times.shift
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# for debugging, really
|
106
|
+
def to_s
|
107
|
+
<<~TO_S
|
108
|
+
limit: #{@limit} per #{@per} seconds
|
109
|
+
at_capacity?: #{at_capacity?}
|
110
|
+
used_capacity: #{used_capacity}
|
111
|
+
total_jobs: #{@total_jobs}
|
112
|
+
dropped_jobs: #{@dropped_jobs}
|
113
|
+
errored_jobs: #{@errored_jobs}
|
114
|
+
TO_S
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class TinyRlExceededRateLimitError < RuntimeError; end
|
119
|
+
class TinyRlInvalidStrategyError < RuntimeError; end
|