glutton_ratelimit 0.2.0 → 1.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 +7 -0
- data/.document +5 -5
- data/.github/workflows/ruby.yml +20 -0
- data/.ruby-version +1 -0
- data/.travis.yml +9 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +63 -0
- data/LICENSE +23 -23
- data/README.rdoc +156 -130
- data/Rakefile +50 -52
- data/VERSION +1 -1
- data/examples/limit_instance_methods.rb +33 -33
- data/examples/simple_manual.rb +31 -31
- data/glutton_ratelimit.gemspec +64 -64
- data/lib/glutton_ratelimit.rb +39 -38
- data/lib/glutton_ratelimit/averaged_throttle.rb +45 -44
- data/lib/glutton_ratelimit/bursty_ring_buffer.rb +24 -24
- data/lib/glutton_ratelimit/bursty_token_bucket.rb +30 -26
- data/test/helper.rb +20 -20
- data/test/test_glutton_ratelimit_averaged_throttle.rb +15 -15
- data/test/test_glutton_ratelimit_bursty_ring_buffer.rb +15 -15
- data/test/test_glutton_ratelimit_bursty_token_bucket.rb +15 -15
- data/test/testing_module.rb +73 -69
- metadata +56 -43
data/Rakefile
CHANGED
@@ -1,52 +1,50 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'rake'
|
3
|
-
|
4
|
-
begin
|
5
|
-
require 'jeweler'
|
6
|
-
Jeweler::Tasks.new do |gem|
|
7
|
-
gem.name = "glutton_ratelimit"
|
8
|
-
gem.summary = %Q{Simple Ruby library for self-imposed rater-limiting.}
|
9
|
-
gem.description = %Q{A Ruby library for limiting the number of times a method can be invoked within a specified time period.}
|
10
|
-
gem.email = "stungeye@gmail.com"
|
11
|
-
gem.homepage = "http://github.com/stungeye/glutton_ratelimit"
|
12
|
-
gem.authors = ["Wally Glutton"]
|
13
|
-
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
14
|
-
end
|
15
|
-
Jeweler::GemcutterTasks.new
|
16
|
-
rescue LoadError
|
17
|
-
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
18
|
-
end
|
19
|
-
|
20
|
-
require 'rake/testtask'
|
21
|
-
Rake::TestTask.new(:test) do |test|
|
22
|
-
test.libs << 'lib' << 'test'
|
23
|
-
test.pattern = 'test/**/test_*.rb'
|
24
|
-
test.verbose = true
|
25
|
-
end
|
26
|
-
|
27
|
-
begin
|
28
|
-
require 'rcov/rcovtask'
|
29
|
-
Rcov::RcovTask.new do |test|
|
30
|
-
test.libs << 'test'
|
31
|
-
test.pattern = 'test/**/test_*.rb'
|
32
|
-
test.verbose = true
|
33
|
-
end
|
34
|
-
rescue LoadError
|
35
|
-
task :rcov do
|
36
|
-
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
task :
|
41
|
-
|
42
|
-
task
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
rdoc.
|
49
|
-
rdoc.
|
50
|
-
|
51
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
52
|
-
end
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "glutton_ratelimit"
|
8
|
+
gem.summary = %Q{Simple Ruby library for self-imposed rater-limiting.}
|
9
|
+
gem.description = %Q{A Ruby library for limiting the number of times a method can be invoked within a specified time period.}
|
10
|
+
gem.email = "stungeye@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/stungeye/glutton_ratelimit"
|
12
|
+
gem.authors = ["Wally Glutton"]
|
13
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
14
|
+
end
|
15
|
+
Jeweler::GemcutterTasks.new
|
16
|
+
rescue LoadError
|
17
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'rake/testtask'
|
21
|
+
Rake::TestTask.new(:test) do |test|
|
22
|
+
test.libs << 'lib' << 'test'
|
23
|
+
test.pattern = 'test/**/test_*.rb'
|
24
|
+
test.verbose = true
|
25
|
+
end
|
26
|
+
|
27
|
+
begin
|
28
|
+
require 'rcov/rcovtask'
|
29
|
+
Rcov::RcovTask.new do |test|
|
30
|
+
test.libs << 'test'
|
31
|
+
test.pattern = 'test/**/test_*.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
rescue LoadError
|
35
|
+
task :rcov do
|
36
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
task :default => :test
|
41
|
+
|
42
|
+
require 'rdoc/task'
|
43
|
+
Rake::RDocTask.new do |rdoc|
|
44
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
45
|
+
|
46
|
+
rdoc.rdoc_dir = 'rdoc'
|
47
|
+
rdoc.title = "glutton_ratelimit #{version}"
|
48
|
+
rdoc.rdoc_files.include('README*')
|
49
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
50
|
+
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
1.0.0
|
@@ -1,34 +1,34 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'glutton_ratelimit'
|
3
|
-
|
4
|
-
class LimitInstanceMethods
|
5
|
-
extend GluttonRatelimit
|
6
|
-
|
7
|
-
def initialize
|
8
|
-
@start = Time.now
|
9
|
-
end
|
10
|
-
|
11
|
-
def limit_me
|
12
|
-
puts "#{Time.now - @start}"
|
13
|
-
sleep 0.001
|
14
|
-
end
|
15
|
-
|
16
|
-
def cap_me
|
17
|
-
puts "#{Time.now - @start}"
|
18
|
-
sleep 0.001
|
19
|
-
end
|
20
|
-
|
21
|
-
rate_limit :limit_me, 6, 6
|
22
|
-
rate_limit :cap_me, 6, 6, GluttonRatelimit::BurstyTokenBucket
|
23
|
-
end
|
24
|
-
|
25
|
-
t = LimitInstanceMethods.new
|
26
|
-
|
27
|
-
puts "Six requests every 6 seconds (Averaged): "
|
28
|
-
13.times { t.limit_me }
|
29
|
-
puts "Six requests every 6 seconds (Bursty): "
|
30
|
-
13.times { t.cap_me }
|
31
|
-
|
32
|
-
# In both cases:
|
33
|
-
# The 7th execution should occur after 6 seconds after the first.
|
1
|
+
require 'rubygems'
|
2
|
+
require 'glutton_ratelimit'
|
3
|
+
|
4
|
+
class LimitInstanceMethods
|
5
|
+
extend GluttonRatelimit
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@start = Time.now
|
9
|
+
end
|
10
|
+
|
11
|
+
def limit_me
|
12
|
+
puts "#{Time.now - @start}"
|
13
|
+
sleep 0.001
|
14
|
+
end
|
15
|
+
|
16
|
+
def cap_me
|
17
|
+
puts "#{Time.now - @start}"
|
18
|
+
sleep 0.001
|
19
|
+
end
|
20
|
+
|
21
|
+
rate_limit :limit_me, 6, 6
|
22
|
+
rate_limit :cap_me, 6, 6, GluttonRatelimit::BurstyTokenBucket
|
23
|
+
end
|
24
|
+
|
25
|
+
t = LimitInstanceMethods.new
|
26
|
+
|
27
|
+
puts "Six requests every 6 seconds (Averaged): "
|
28
|
+
13.times { t.limit_me }
|
29
|
+
puts "Six requests every 6 seconds (Bursty): "
|
30
|
+
13.times { t.cap_me }
|
31
|
+
|
32
|
+
# In both cases:
|
33
|
+
# The 7th execution should occur after 6 seconds after the first.
|
34
34
|
# The 13th execution should occur after 12 seconds after the first.
|
data/examples/simple_manual.rb
CHANGED
@@ -1,32 +1,32 @@
|
|
1
|
-
# $LOAD_PATH << File.dirname(__FILE__) +'/../lib'
|
2
|
-
require 'rubygems'
|
3
|
-
require 'glutton_ratelimit'
|
4
|
-
|
5
|
-
puts "Maximum of 12 executions every 5 seconds (Bursty):"
|
6
|
-
rl = GluttonRatelimit::BurstyTokenBucket.new 12, 5
|
7
|
-
|
8
|
-
start = Time.now
|
9
|
-
n = 0
|
10
|
-
|
11
|
-
rl.times(25) do
|
12
|
-
puts "#{n += 1} - #{Time.now - start}"
|
13
|
-
# Simulating a constant-time task:
|
14
|
-
sleep 0.1
|
15
|
-
end
|
16
|
-
|
17
|
-
# The 25th execution should occur after 10 seconds has elapsed.
|
18
|
-
|
19
|
-
puts "Maximum of 3 executions every 3 seconds (Averaged):"
|
20
|
-
rl = GluttonRatelimit::AveragedThrottle.new 3, 3
|
21
|
-
# AverageThrottle will attempt to evenly space executions within the allowed period.
|
22
|
-
|
23
|
-
start = Time.now
|
24
|
-
n = 0
|
25
|
-
|
26
|
-
rl.times(7) do
|
27
|
-
puts "#{n += 1} - #{Time.now - start}"
|
28
|
-
# Simulating a 0 to 1 second random-time task:
|
29
|
-
sleep rand
|
30
|
-
end
|
31
|
-
|
1
|
+
# $LOAD_PATH << File.dirname(__FILE__) +'/../lib'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'glutton_ratelimit'
|
4
|
+
|
5
|
+
puts "Maximum of 12 executions every 5 seconds (Bursty):"
|
6
|
+
rl = GluttonRatelimit::BurstyTokenBucket.new 12, 5
|
7
|
+
|
8
|
+
start = Time.now
|
9
|
+
n = 0
|
10
|
+
|
11
|
+
rl.times(25) do
|
12
|
+
puts "#{n += 1} - #{Time.now - start}"
|
13
|
+
# Simulating a constant-time task:
|
14
|
+
sleep 0.1
|
15
|
+
end
|
16
|
+
|
17
|
+
# The 25th execution should occur after 10 seconds has elapsed.
|
18
|
+
|
19
|
+
puts "Maximum of 3 executions every 3 seconds (Averaged):"
|
20
|
+
rl = GluttonRatelimit::AveragedThrottle.new 3, 3
|
21
|
+
# AverageThrottle will attempt to evenly space executions within the allowed period.
|
22
|
+
|
23
|
+
start = Time.now
|
24
|
+
n = 0
|
25
|
+
|
26
|
+
rl.times(7) do
|
27
|
+
puts "#{n += 1} - #{Time.now - start}"
|
28
|
+
# Simulating a 0 to 1 second random-time task:
|
29
|
+
sleep rand
|
30
|
+
end
|
31
|
+
|
32
32
|
# The 7th execution should occur after 6 seconds has elapsed.
|
data/glutton_ratelimit.gemspec
CHANGED
@@ -1,64 +1,64 @@
|
|
1
|
-
# Generated by jeweler
|
2
|
-
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in
|
4
|
-
# -*- encoding: utf-8 -*-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
s.
|
9
|
-
|
10
|
-
|
11
|
-
s.
|
12
|
-
s.
|
13
|
-
s.
|
14
|
-
s.
|
15
|
-
s.
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
# stub: glutton_ratelimit 1.0.0 ruby lib
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "glutton_ratelimit".freeze
|
9
|
+
s.version = "1.0.0"
|
10
|
+
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
|
+
s.require_paths = ["lib".freeze]
|
13
|
+
s.authors = ["Wally Glutton".freeze]
|
14
|
+
s.date = "2019-12-19"
|
15
|
+
s.description = "A Ruby library for limiting the number of times a method can be invoked within a specified time period.".freeze
|
16
|
+
s.email = "stungeye@gmail.com".freeze
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE",
|
19
|
+
"README.rdoc"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".document",
|
23
|
+
".github/workflows/ruby.yml",
|
24
|
+
".ruby-version",
|
25
|
+
".travis.yml",
|
26
|
+
"Gemfile",
|
27
|
+
"Gemfile.lock",
|
28
|
+
"LICENSE",
|
29
|
+
"README.rdoc",
|
30
|
+
"Rakefile",
|
31
|
+
"VERSION",
|
32
|
+
"examples/limit_instance_methods.rb",
|
33
|
+
"examples/simple_manual.rb",
|
34
|
+
"glutton_ratelimit.gemspec",
|
35
|
+
"lib/glutton_ratelimit.rb",
|
36
|
+
"lib/glutton_ratelimit/averaged_throttle.rb",
|
37
|
+
"lib/glutton_ratelimit/bursty_ring_buffer.rb",
|
38
|
+
"lib/glutton_ratelimit/bursty_token_bucket.rb",
|
39
|
+
"test/helper.rb",
|
40
|
+
"test/test_glutton_ratelimit_averaged_throttle.rb",
|
41
|
+
"test/test_glutton_ratelimit_bursty_ring_buffer.rb",
|
42
|
+
"test/test_glutton_ratelimit_bursty_token_bucket.rb",
|
43
|
+
"test/testing_module.rb"
|
44
|
+
]
|
45
|
+
s.homepage = "http://github.com/stungeye/glutton_ratelimit".freeze
|
46
|
+
s.rubygems_version = "2.7.6.2".freeze
|
47
|
+
s.summary = "Simple Ruby library for self-imposed rater-limiting.".freeze
|
48
|
+
|
49
|
+
if s.respond_to? :specification_version then
|
50
|
+
s.specification_version = 4
|
51
|
+
|
52
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
53
|
+
s.add_runtime_dependency(%q<jeweler>.freeze, [">= 0"])
|
54
|
+
s.add_runtime_dependency(%q<test-unit>.freeze, [">= 0"])
|
55
|
+
else
|
56
|
+
s.add_dependency(%q<jeweler>.freeze, [">= 0"])
|
57
|
+
s.add_dependency(%q<test-unit>.freeze, [">= 0"])
|
58
|
+
end
|
59
|
+
else
|
60
|
+
s.add_dependency(%q<jeweler>.freeze, [">= 0"])
|
61
|
+
s.add_dependency(%q<test-unit>.freeze, [">= 0"])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
data/lib/glutton_ratelimit.rb
CHANGED
@@ -1,38 +1,39 @@
|
|
1
|
-
module GluttonRatelimit
|
2
|
-
|
3
|
-
def rate_limit symbol, executions, time_period, rl_class = AveragedThrottle
|
4
|
-
rl = rl_class.new executions, time_period
|
5
|
-
old_symbol = "#{symbol}_old".to_sym
|
6
|
-
alias_method old_symbol, symbol
|
7
|
-
define_method symbol do |*args|
|
8
|
-
rl.wait
|
9
|
-
self.send old_symbol, *args
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
private
|
14
|
-
# All the other classes extend this parent and are therefore
|
15
|
-
# constructed in the same manner.
|
16
|
-
class ParentLimiter
|
17
|
-
attr_reader :executions
|
18
|
-
|
19
|
-
def initialize executions, time_period
|
20
|
-
@executions = executions
|
21
|
-
@time_period = time_period
|
22
|
-
end
|
23
|
-
|
24
|
-
def times(num, &block)
|
25
|
-
raise ArgumentError, "Code block expected" if not block
|
26
|
-
|
27
|
-
num.
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
require File.join(dir, "glutton_ratelimit", "
|
38
|
-
require File.join(dir, "glutton_ratelimit", "
|
1
|
+
module GluttonRatelimit
|
2
|
+
|
3
|
+
def rate_limit symbol, executions, time_period, rl_class = AveragedThrottle
|
4
|
+
rl = rl_class.new executions, time_period
|
5
|
+
old_symbol = "#{symbol}_old".to_sym
|
6
|
+
alias_method old_symbol, symbol
|
7
|
+
define_method symbol do |*args|
|
8
|
+
rl.wait
|
9
|
+
self.send old_symbol, *args
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
# All the other classes extend this parent and are therefore
|
15
|
+
# constructed in the same manner.
|
16
|
+
class ParentLimiter
|
17
|
+
attr_reader :executions
|
18
|
+
|
19
|
+
def initialize executions, time_period
|
20
|
+
@executions = executions
|
21
|
+
@time_period = time_period
|
22
|
+
end
|
23
|
+
|
24
|
+
def times(num, &block)
|
25
|
+
raise ArgumentError, "Code block expected" if not block
|
26
|
+
|
27
|
+
raise ArgumentError, "Parameter expected to be #{Integer.to_s} but found a #{num.class}." unless num.kind_of?(Integer)
|
28
|
+
num.times do
|
29
|
+
wait
|
30
|
+
yield
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
dir = File.expand_path(File.dirname(__FILE__))
|
37
|
+
require File.join(dir, "glutton_ratelimit", "bursty_ring_buffer")
|
38
|
+
require File.join(dir, "glutton_ratelimit", "bursty_token_bucket")
|
39
|
+
require File.join(dir, "glutton_ratelimit", "averaged_throttle")
|
@@ -1,44 +1,45 @@
|
|
1
|
-
module GluttonRatelimit
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
@
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
end
|
43
|
-
|
44
|
-
|
1
|
+
module GluttonRatelimit
|
2
|
+
class AveragedThrottle < ParentLimiter
|
3
|
+
def initialize executions, time_period
|
4
|
+
super(executions, time_period)
|
5
|
+
@tokens = nil
|
6
|
+
end
|
7
|
+
|
8
|
+
def reset_bucket
|
9
|
+
@before_previous_execution ||= Time.now
|
10
|
+
@oldest_timestamp = Time.now
|
11
|
+
@tokens = @executions
|
12
|
+
@total_task_time = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def executed_this_period
|
16
|
+
@executions - @tokens
|
17
|
+
end
|
18
|
+
|
19
|
+
def average_task_time
|
20
|
+
@total_task_time.to_f / executed_this_period
|
21
|
+
end
|
22
|
+
|
23
|
+
def wait
|
24
|
+
reset_bucket if @tokens.nil?
|
25
|
+
|
26
|
+
now = Time.now
|
27
|
+
delta_since_previous = now - @before_previous_execution
|
28
|
+
@total_task_time += delta_since_previous
|
29
|
+
remaining_time = @time_period - (now - @oldest_timestamp)
|
30
|
+
|
31
|
+
if @tokens.zero?
|
32
|
+
sleep(remaining_time) if remaining_time > 0
|
33
|
+
reset_bucket
|
34
|
+
elsif executed_this_period != 0
|
35
|
+
throttle = (remaining_time.to_f + delta_since_previous) / (@tokens+1) - average_task_time
|
36
|
+
sleep throttle if throttle > 0
|
37
|
+
end
|
38
|
+
|
39
|
+
@tokens -= 1
|
40
|
+
@before_previous_execution = Time.now
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|