progress-logger 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.
- data/.document +5 -0
- data/.gitignore +24 -0
- data/LICENSE +20 -0
- data/README.rdoc +45 -0
- data/Rakefile +46 -0
- data/VERSION +1 -0
- data/lib/progress-logger.rb +210 -0
- data/progress-logger.gemspec +58 -0
- data/spec/progress-logger_spec.rb +270 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- metadata +105 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Kornelis Sietsma
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
= progress-logger
|
2
|
+
|
3
|
+
This is a very simple gem to make it easier to log the state of long slow processing jobs.
|
4
|
+
I tend to do this sort of thing over and over and over:
|
5
|
+
count = 0
|
6
|
+
@collection.each do |record|
|
7
|
+
count += 1
|
8
|
+
if count % 1000000 == 0
|
9
|
+
puts "Processed #{count} rows"
|
10
|
+
end
|
11
|
+
# do stuff
|
12
|
+
end
|
13
|
+
with variations for timed reporting, calculating processing rate, and so on.
|
14
|
+
ProgressLogger really doesn't do much - it just tries to handle the interval checking, leaving what you do every
|
15
|
+
million records (or whatever) entirely up to you. The example above can be run as:
|
16
|
+
|
17
|
+
p = ProgressLogger.new(:count => 1000000) do |state|
|
18
|
+
puts "Processed #{state.count} rows"
|
19
|
+
end
|
20
|
+
@collection.each do |record|
|
21
|
+
p.trigger
|
22
|
+
# do stuff
|
23
|
+
end
|
24
|
+
|
25
|
+
You can do lots of other stuff too - see the ProgressLogger rdocs for more, but an example to whet your appetite:
|
26
|
+
p = ProgressLogger.new(:count => 1000000, :minutes => 30, :max => @collection.size) do |state|
|
27
|
+
@logger.info("Processed #{state.count} rows - #{state.long_eta/(60*60)} hours to go!")
|
28
|
+
@cache.flush()
|
29
|
+
end
|
30
|
+
|
31
|
+
More examples are in the ProgressLogger docs - or take a look at the specs!
|
32
|
+
|
33
|
+
== Note on Patches/Pull Requests
|
34
|
+
|
35
|
+
* Fork the project.
|
36
|
+
* Make your feature addition or bug fix.
|
37
|
+
* Add tests for it. This is important so I don't break it in a
|
38
|
+
future version unintentionally.
|
39
|
+
* Commit, do not mess with rakefile, version, or history.
|
40
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
41
|
+
* Send me a pull request. Bonus points for topic branches.
|
42
|
+
|
43
|
+
== Copyright
|
44
|
+
|
45
|
+
Copyright (c) 2010 Kornelis Sietsma. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "progress-logger"
|
8
|
+
gem.summary = %Q{Simple gem for regular progress logging in a long-running loop}
|
9
|
+
gem.description = %Q{Simple gem for regular progress logging in a long-running loop, log every N actions, or based on a time interval}
|
10
|
+
gem.email = "korny@sietsma.com"
|
11
|
+
gem.homepage = "http://github.com/kornysietsma/progress-logger"
|
12
|
+
gem.authors = ["Kornelis Sietsma"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
gem.add_development_dependency "delorean", ">= 0.2.0"
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'spec/rake/spectask'
|
23
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
24
|
+
spec.libs << 'lib' << 'spec'
|
25
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
26
|
+
end
|
27
|
+
|
28
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
29
|
+
spec.libs << 'lib' << 'spec'
|
30
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
31
|
+
spec.rcov = true
|
32
|
+
end
|
33
|
+
|
34
|
+
task :spec => :check_dependencies
|
35
|
+
|
36
|
+
task :default => :spec
|
37
|
+
|
38
|
+
require 'rake/rdoctask'
|
39
|
+
Rake::RDocTask.new do |rdoc|
|
40
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
41
|
+
|
42
|
+
rdoc.rdoc_dir = 'rdoc'
|
43
|
+
rdoc.title = "progress-logger #{version}"
|
44
|
+
rdoc.rdoc_files.include('README*')
|
45
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
46
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
@@ -0,0 +1,210 @@
|
|
1
|
+
# ProgressLogger is a simple tool for regular progress logging in a long-running loop
|
2
|
+
#
|
3
|
+
# Author:: Kornelis Sietsma (mailto: korny@sietsma.com)
|
4
|
+
# Copyright:: Copyright (c) 2010 Kornelis Sietsma. See LICENSE for details.
|
5
|
+
|
6
|
+
# The ProgressLogger class is the workhorse of this gem - it is used to wrap your logging code (or whatever you are doing)
|
7
|
+
# you construct it with a set of criteria for when it should log, and a block to do the actual logging (or other activity):
|
8
|
+
# <tt>
|
9
|
+
# p = ProgressLogger.new(:step => 100000) do |state|
|
10
|
+
# puts "processed #{state.count} rows"
|
11
|
+
# end
|
12
|
+
# </tt>
|
13
|
+
# and then every time "p.trigger()" is called:
|
14
|
+
# * p.count is incremented
|
15
|
+
# * if p.count is a multiple of 100000 the block is called, with a 'state' object as a parameter
|
16
|
+
#
|
17
|
+
# You can do pretty well anything you want in the passed block - log something, flush a database, update a gui, whatever.
|
18
|
+
#
|
19
|
+
# See the ProgressLogger::State class for what you can get from the state object
|
20
|
+
#
|
21
|
+
# == Examples
|
22
|
+
#
|
23
|
+
# ==== Log every 5 minutes:
|
24
|
+
# <tt>
|
25
|
+
# p = ProgressLogger.new(:minutes => 5) do |state|
|
26
|
+
# puts "processed #{state.count} rows"
|
27
|
+
# end
|
28
|
+
# </tt>
|
29
|
+
#
|
30
|
+
# ==== Log every hour, or after a million triggers, with a detailed message and some extra work
|
31
|
+
# <tt>
|
32
|
+
# max = @collection.size
|
33
|
+
# p = ProgressLogger.new(:hours => 1, :step => 1000000, :max => max) do |state|
|
34
|
+
# @logger.info "processed #{state.count} rows"
|
35
|
+
# @logger.info " Current processing rate of #{state.short_rate} rows/sec implies ending in #{state.short_eta/(3600)} hours"
|
36
|
+
# @logger.info " Long-term processing rate of #{state.long_rate} rows/sec implies ending in #{state.long_eta/(3600)} hours"
|
37
|
+
# @logger.debug "flushing cache"
|
38
|
+
# @cache.flush
|
39
|
+
# end
|
40
|
+
# @collection.find().each do |row|
|
41
|
+
# p.trigger
|
42
|
+
# process(row)
|
43
|
+
# end
|
44
|
+
# @logger.info "done - processed #{p.count} rows in total"
|
45
|
+
# </tt>
|
46
|
+
#
|
47
|
+
# ==== Passing the ProgressLogger around
|
48
|
+
#
|
49
|
+
# <tt>
|
50
|
+
# parent_count = 0
|
51
|
+
# plogger = ProgressLogger.new(:minutes => 5) do |state|
|
52
|
+
# puts "processed #{parent_count} parents, #{state.count} children"
|
53
|
+
# end
|
54
|
+
# @parents.each do |parent|
|
55
|
+
# parent_count += 1
|
56
|
+
# process_children(parent, plogger)
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
# def process_children(parent, plogger)
|
60
|
+
# parent.children.each do |child|
|
61
|
+
# plogger.trigger
|
62
|
+
# ... do stuff
|
63
|
+
# end
|
64
|
+
# end
|
65
|
+
# </tt>
|
66
|
+
|
67
|
+
class ProgressLogger
|
68
|
+
|
69
|
+
# Internal state passed to the ProgressLogger block whenever the criteria are met.
|
70
|
+
# count is the current count of triggers processed
|
71
|
+
# now is the Time.now value when this action was triggered (useful if you are handling state slowly)
|
72
|
+
class State
|
73
|
+
attr_reader :count, :now
|
74
|
+
# the number of triggers processed since the last action
|
75
|
+
def count_delta
|
76
|
+
@count - @last_count
|
77
|
+
end
|
78
|
+
# the amount of time passed since the first trigger (or since ProgressLogger.start was called)
|
79
|
+
def time_total
|
80
|
+
@now - @start_time
|
81
|
+
end
|
82
|
+
# the amount of time passed since the last action
|
83
|
+
def time_delta
|
84
|
+
@now - @last_report
|
85
|
+
end
|
86
|
+
# the processing rate, in triggers-per-second, using the count since the last action for a short-term speed
|
87
|
+
# * will return nil if this is called on the very first call to trigger, as no time will have elapsed!
|
88
|
+
def short_rate
|
89
|
+
return nil if @now == @last_report
|
90
|
+
(@count - @last_count) / (1.0 * (@now - @last_report))
|
91
|
+
end
|
92
|
+
# the processing rate, in triggers-per-second, using the count since the first action for a long-term speed
|
93
|
+
# * will return nil if this is called on the very first call to trigger, as no time will have elapsed!
|
94
|
+
def long_rate
|
95
|
+
return nil if @now == @start_time
|
96
|
+
(@count - @start_count) / (1.0 * (@now - @start_time))
|
97
|
+
end
|
98
|
+
# the estimated time to complete, based on the short-term speed (short_rate)
|
99
|
+
# * raises ArgumentError if you don't have a :max parameter in the main ProgressLogger constructor
|
100
|
+
# * will return nil if this is called on the very first call to trigger, as no time will have elapsed!
|
101
|
+
def short_eta
|
102
|
+
raise ArgumentError.new("Can't calculate ETA when no max specified") if @max.nil?
|
103
|
+
rate = short_rate
|
104
|
+
return nil if rate.nil?
|
105
|
+
return (@max - @count) / rate
|
106
|
+
end
|
107
|
+
# the estimated time to complete, based on the long-term speed (long_rate)
|
108
|
+
# * raises ArgumentError if you don't have a :max parameter in the main ProgressLogger constructor
|
109
|
+
# * will return nil if this is called on the very first call to trigger, as no time will have elapsed!
|
110
|
+
def long_eta
|
111
|
+
raise ArgumentError.new("Can't calculate ETA when no max specified") if @max.nil?
|
112
|
+
rate = long_rate
|
113
|
+
return nil if rate.nil?
|
114
|
+
return (@max - @count) / rate
|
115
|
+
end
|
116
|
+
private
|
117
|
+
def initialize(count, start_count, now, start_time, last_report, last_count, max = nil) #:notnew:
|
118
|
+
@count = count
|
119
|
+
@start_count = start_count
|
120
|
+
@now = now
|
121
|
+
@start_time = start_time
|
122
|
+
@last_report = last_report
|
123
|
+
@last_count = last_count
|
124
|
+
@max = max
|
125
|
+
end
|
126
|
+
end
|
127
|
+
# count of triggers processed so far
|
128
|
+
attr_reader :count
|
129
|
+
|
130
|
+
# create a ProgressLogger with specified criteria
|
131
|
+
# you *must* specify either a :step or one of :seconds, :minutes, and/or :hours
|
132
|
+
# parameters:
|
133
|
+
# * :step - the passed block is called after this many calls to trigger()
|
134
|
+
# * :seconds, :minutes, :hours - the passed block is called after this number of seconds/minutes/hours
|
135
|
+
# * * you can specify more than one of these, they'll just get added together
|
136
|
+
# * :max - this is an expected maximum number of triggers - it's used to calculate eta values for the ProgressLogger::State object
|
137
|
+
# * block - you must pass a block, it is called (with a state parameter) when the above criteria are met
|
138
|
+
|
139
|
+
def initialize(params = {}, &block)
|
140
|
+
unless params[:step] || params[:seconds] || params[:minutes] || params[:hours]
|
141
|
+
raise ArgumentError.new("You must specify a :step, :seconds, :minutes or :hours interval criterion to ProgressLogger")
|
142
|
+
end
|
143
|
+
unless block_given?
|
144
|
+
raise ArgumentError.new("You must pass a block to ProgressLogger")
|
145
|
+
end
|
146
|
+
@stepsize = params[:step]
|
147
|
+
raise ArgumentError.new("Step size must be greater than 0") if @stepsize && @stepsize <= 0
|
148
|
+
@max = params[:max]
|
149
|
+
raise ArgumentError.new("Max count must be greater than 0") if @max && @max <= 0
|
150
|
+
|
151
|
+
@count_based = params[:step]
|
152
|
+
@time_based = params[:seconds] || params[:minutes] || params[:hours]
|
153
|
+
if @time_based
|
154
|
+
@seconds = params[:seconds] || 0
|
155
|
+
@seconds += params[:minutes] * 60 if params[:minutes]
|
156
|
+
@seconds += params[:hours] * 60 * 60 if params[:hours]
|
157
|
+
raise ArgumentError.new("You must specify a total time greater than 0") if @seconds <= 0
|
158
|
+
end
|
159
|
+
|
160
|
+
@count = 0
|
161
|
+
@block = block
|
162
|
+
@started = false # don't start yet - allow for startup time in loops; can start manually with start() below
|
163
|
+
end
|
164
|
+
|
165
|
+
# manually start timers
|
166
|
+
# normally timers are initialized on the first call to trigger() - this is because quite often, a processing loop
|
167
|
+
# like the following has a big startup time as cursors are allocated etc:
|
168
|
+
# <tt>
|
169
|
+
# p = ProgressLogger.new ...
|
170
|
+
# @db.find({:widget => true).each do # this takes 5 minutes to cache cursors!
|
171
|
+
# p.trigger
|
172
|
+
# </tt>
|
173
|
+
# If the timers were initialized when ProgressLogger.new was called, they'd be messed up by the loop start time.
|
174
|
+
# If for some reason you want the timers to be manually started earlier, you can explicitly call start,
|
175
|
+
# optionally passing it your own special version of Time.now
|
176
|
+
def start(now = Time.now)
|
177
|
+
@started = true
|
178
|
+
@start_time = now
|
179
|
+
@last_report = now
|
180
|
+
@last_timecheck = now # last time interval, so count-based reports don't stop time-based reports
|
181
|
+
@start_count = @last_count = @count
|
182
|
+
end
|
183
|
+
|
184
|
+
# trigger whatever regular event you are watching - if the criteria are met, this will call your block of code
|
185
|
+
def trigger
|
186
|
+
start unless @started # note - will set start and last counts to 0, which may be slightly inaccurate!
|
187
|
+
@count += 1
|
188
|
+
now = Time.now
|
189
|
+
if @time_based
|
190
|
+
time_delta = now - @last_timecheck
|
191
|
+
its_time = (time_delta > @seconds)
|
192
|
+
else
|
193
|
+
its_time = false
|
194
|
+
end
|
195
|
+
its_enough = @count_based && (@count % @stepsize == 0)
|
196
|
+
if its_time || its_enough
|
197
|
+
run_block now
|
198
|
+
@last_report = now
|
199
|
+
@last_count = @count
|
200
|
+
@last_timecheck = now if its_time
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
private
|
205
|
+
|
206
|
+
def run_block(now)
|
207
|
+
@block.call(State.new(@count, @start_count, now, @start_time, @last_report, @last_count, @max))
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{progress-logger}
|
8
|
+
s.version = "1.0.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Kornelis Sietsma"]
|
12
|
+
s.date = %q{2010-10-27}
|
13
|
+
s.description = %q{Simple gem for regular progress logging in a long-running loop, log every N actions, or based on a time interval}
|
14
|
+
s.email = %q{korny@sietsma.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lib/progress-logger.rb",
|
27
|
+
"progress-logger.gemspec",
|
28
|
+
"spec/progress-logger_spec.rb",
|
29
|
+
"spec/spec.opts",
|
30
|
+
"spec/spec_helper.rb"
|
31
|
+
]
|
32
|
+
s.homepage = %q{http://github.com/kornysietsma/progress-logger}
|
33
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
34
|
+
s.require_paths = ["lib"]
|
35
|
+
s.rubygems_version = %q{1.3.7}
|
36
|
+
s.summary = %q{Simple gem for regular progress logging in a long-running loop}
|
37
|
+
s.test_files = [
|
38
|
+
"spec/progress-logger_spec.rb",
|
39
|
+
"spec/spec_helper.rb"
|
40
|
+
]
|
41
|
+
|
42
|
+
if s.respond_to? :specification_version then
|
43
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
44
|
+
s.specification_version = 3
|
45
|
+
|
46
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
47
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
48
|
+
s.add_development_dependency(%q<delorean>, [">= 0.2.0"])
|
49
|
+
else
|
50
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
51
|
+
s.add_dependency(%q<delorean>, [">= 0.2.0"])
|
52
|
+
end
|
53
|
+
else
|
54
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
55
|
+
s.add_dependency(%q<delorean>, [">= 0.2.0"])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
@@ -0,0 +1,270 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe ProgressLogger do
|
4
|
+
before :each do
|
5
|
+
@event_count = 0 # count used all over the place to check number of block calls made
|
6
|
+
end
|
7
|
+
context "when handling invalid parameters" do
|
8
|
+
it "should throw an exception if not passed any step criteria" do
|
9
|
+
lambda {
|
10
|
+
ProgressLogger.new
|
11
|
+
}.should raise_error ArgumentError
|
12
|
+
end
|
13
|
+
it "should throw an exception if not passed a block" do
|
14
|
+
lambda {
|
15
|
+
ProgressLogger.new(:step => 1)
|
16
|
+
}.should raise_error ArgumentError
|
17
|
+
end
|
18
|
+
it "should throw an exception if step size is zero or less" do
|
19
|
+
lambda {
|
20
|
+
ProgressLogger.new(:step => 0) {}
|
21
|
+
}.should raise_error ArgumentError
|
22
|
+
lambda {
|
23
|
+
ProgressLogger.new(:step => -1) {}
|
24
|
+
}.should raise_error ArgumentError
|
25
|
+
end
|
26
|
+
it "should throw an exception if step time is zero or less" do
|
27
|
+
lambda {
|
28
|
+
ProgressLogger.new(:seconds => 0) {}
|
29
|
+
}.should raise_error ArgumentError
|
30
|
+
lambda {
|
31
|
+
ProgressLogger.new(:minutes => 0) {}
|
32
|
+
}.should raise_error ArgumentError
|
33
|
+
lambda {
|
34
|
+
ProgressLogger.new(:hours => 0) {}
|
35
|
+
}.should raise_error ArgumentError
|
36
|
+
end
|
37
|
+
it "should throw an exception if total step time is negative" do
|
38
|
+
ProgressLogger.new(:seconds => 61, :minutes => -1) {} # weird, but ok
|
39
|
+
lambda {
|
40
|
+
ProgressLogger.new(:seconds => 60, :minutes => -1) {}
|
41
|
+
}.should raise_error ArgumentError
|
42
|
+
end
|
43
|
+
it "should throw an exception if max count is zero or less" do
|
44
|
+
lambda {
|
45
|
+
ProgressLogger.new(:step => 1, :max => 0) {}
|
46
|
+
}.should raise_error ArgumentError
|
47
|
+
lambda {
|
48
|
+
ProgressLogger.new(:step => 1, :max => -1) {}
|
49
|
+
}.should raise_error ArgumentError
|
50
|
+
end
|
51
|
+
it "should not allow calculating an ETA without a max count specified" do
|
52
|
+
p = ProgressLogger.new(:step => 1) do |state|
|
53
|
+
puts state.short_eta
|
54
|
+
end
|
55
|
+
lambda {
|
56
|
+
p.trigger
|
57
|
+
}.should raise_error ArgumentError
|
58
|
+
p2 = ProgressLogger.new(:step => 1) do |state|
|
59
|
+
puts state.long_eta
|
60
|
+
end
|
61
|
+
lambda {
|
62
|
+
p2.trigger
|
63
|
+
}.should raise_error ArgumentError
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "with numeric step size" do
|
69
|
+
it "should call the passed block every time when called with size of one" do
|
70
|
+
p = ProgressLogger.new(:step => 1) do
|
71
|
+
@event_count += 1
|
72
|
+
end
|
73
|
+
p.trigger
|
74
|
+
@event_count.should == 1
|
75
|
+
p.trigger
|
76
|
+
@event_count.should == 2
|
77
|
+
end
|
78
|
+
it "should call the passed block every second times shen called with a size of 2" do
|
79
|
+
p = ProgressLogger.new(:step => 2) do
|
80
|
+
@event_count += 1
|
81
|
+
end
|
82
|
+
p.trigger
|
83
|
+
@event_count.should == 0
|
84
|
+
p.trigger
|
85
|
+
@event_count.should == 1
|
86
|
+
p.trigger
|
87
|
+
@event_count.should == 1
|
88
|
+
p.trigger
|
89
|
+
@event_count.should == 2
|
90
|
+
end
|
91
|
+
it "should let you find the loop count at any time, even when not triggered" do
|
92
|
+
p = ProgressLogger.new(:step => 10000) do
|
93
|
+
@event_count += 1
|
94
|
+
end
|
95
|
+
p.count.should == 0
|
96
|
+
p.trigger
|
97
|
+
@event_count.should == 0 # no trigger yet
|
98
|
+
p.count.should == 1
|
99
|
+
end
|
100
|
+
end
|
101
|
+
context "with time-based step size" do
|
102
|
+
it "should trigger the body once after the specified number of seconds" do
|
103
|
+
p = ProgressLogger.new(:seconds => 10) do
|
104
|
+
@event_count += 1
|
105
|
+
end
|
106
|
+
p.trigger
|
107
|
+
@event_count.should == 0
|
108
|
+
Delorean.jump 10
|
109
|
+
p.trigger
|
110
|
+
@event_count.should == 1
|
111
|
+
end
|
112
|
+
it "should start time counting from the first trigger unless start has been called" do
|
113
|
+
p = ProgressLogger.new(:seconds => 10) do
|
114
|
+
@event_count += 1
|
115
|
+
end
|
116
|
+
Delorean.jump 10
|
117
|
+
p.trigger
|
118
|
+
@event_count.should == 0
|
119
|
+
Delorean.jump 10
|
120
|
+
p.trigger
|
121
|
+
@event_count.should == 1
|
122
|
+
end
|
123
|
+
it "should start counting interval when start is called manually" do
|
124
|
+
p = ProgressLogger.new(:seconds => 10) do
|
125
|
+
@event_count += 1
|
126
|
+
end
|
127
|
+
p.start
|
128
|
+
Delorean.jump 10
|
129
|
+
p.trigger
|
130
|
+
@event_count.should == 1
|
131
|
+
end
|
132
|
+
it "should trigger the body only once even if multiple time intervals have passed" do
|
133
|
+
p = ProgressLogger.new(:seconds => 10) do
|
134
|
+
@event_count += 1
|
135
|
+
end
|
136
|
+
p.trigger
|
137
|
+
Delorean.jump 1000
|
138
|
+
p.trigger
|
139
|
+
@event_count.should == 1
|
140
|
+
end
|
141
|
+
it "should trigger based on interval since last trigger, not total elapsed time (as it's easier)" do
|
142
|
+
p = ProgressLogger.new(:seconds => 10) do
|
143
|
+
@event_count += 1
|
144
|
+
end
|
145
|
+
p.trigger
|
146
|
+
Delorean.jump 1000
|
147
|
+
p.trigger
|
148
|
+
@event_count.should == 1
|
149
|
+
Delorean.jump 10
|
150
|
+
p.trigger
|
151
|
+
@event_count.should == 2
|
152
|
+
end
|
153
|
+
after :each do
|
154
|
+
Delorean.back_to_the_present
|
155
|
+
end
|
156
|
+
end
|
157
|
+
context "with both time and steps" do
|
158
|
+
it "should not let count based intervals interfere with time based" do
|
159
|
+
p = ProgressLogger.new(:step => 2, :seconds => 10) do
|
160
|
+
@event_count += 1
|
161
|
+
end
|
162
|
+
p.trigger
|
163
|
+
Delorean.jump 5
|
164
|
+
p.trigger # fires an event due to step
|
165
|
+
Delorean.jump 5
|
166
|
+
p.trigger # should fire an event due to time, even though it's only 5 seconds since last event
|
167
|
+
@event_count.should == 2
|
168
|
+
end
|
169
|
+
it "should not fire two events if step and time events coincide" do
|
170
|
+
p = ProgressLogger.new(:step => 1, :seconds => 10) do
|
171
|
+
@event_count += 1
|
172
|
+
end
|
173
|
+
p.trigger
|
174
|
+
@event_count.should == 1
|
175
|
+
Delorean.jump 10
|
176
|
+
p.trigger # fires an event due to both criteria
|
177
|
+
@event_count.should == 2
|
178
|
+
end
|
179
|
+
end
|
180
|
+
context "passing context to the included block" do
|
181
|
+
before :each do
|
182
|
+
@states = []
|
183
|
+
@p = ProgressLogger.new(:step => 10) do |state|
|
184
|
+
@states << state
|
185
|
+
end
|
186
|
+
end
|
187
|
+
it "should include the count of triggers called" do
|
188
|
+
20.times {@p.trigger}
|
189
|
+
@states.size.should == 2
|
190
|
+
@states.collect {|s| s.count}.should == [10,20]
|
191
|
+
end
|
192
|
+
it "should include the count change since the last trigger" do
|
193
|
+
20.times {@p.trigger}
|
194
|
+
@states.size.should == 2
|
195
|
+
@states.collect {|s| s.count_delta}.should == [10,10]
|
196
|
+
end
|
197
|
+
it "should include the time since the start time" do
|
198
|
+
@p.start # set initial time
|
199
|
+
20.times do
|
200
|
+
Delorean.jump 1
|
201
|
+
@p.trigger
|
202
|
+
end
|
203
|
+
@states.size.should == 2
|
204
|
+
@states[0].time_total.should be_close(10, 0.1)
|
205
|
+
@states[1].time_total.should be_close(20, 0.1)
|
206
|
+
end
|
207
|
+
it "should include the time since the first trigger if no start specified" do
|
208
|
+
20.times do
|
209
|
+
Delorean.jump 1
|
210
|
+
@p.trigger
|
211
|
+
end
|
212
|
+
@states.size.should == 2
|
213
|
+
@states[0].time_total.should be_close(9, 0.1)
|
214
|
+
@states[1].time_total.should be_close(19, 0.1)
|
215
|
+
end
|
216
|
+
it "should include the delta time since the previous trigger" do
|
217
|
+
@p.start # set initial time
|
218
|
+
20.times do
|
219
|
+
Delorean.jump 1
|
220
|
+
@p.trigger
|
221
|
+
end
|
222
|
+
@states.size.should == 2
|
223
|
+
@states[0].time_delta.should be_close(10, 0.1)
|
224
|
+
@states[1].time_delta.should be_close(10, 0.1)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
context "when dealing with rates and ETAs" do
|
228
|
+
before :each do
|
229
|
+
@states = []
|
230
|
+
@p = ProgressLogger.new(:step => 10, :max => 100) do |state|
|
231
|
+
@states << state
|
232
|
+
end
|
233
|
+
# we can reuse the same scenario for all our tests:
|
234
|
+
@p.trigger # the first trigger is quick, but
|
235
|
+
Delorean.jump 100 # the first interval is a big one - slow startup speed!
|
236
|
+
19.times do
|
237
|
+
Delorean.jump 1
|
238
|
+
@p.trigger
|
239
|
+
end
|
240
|
+
end
|
241
|
+
it "should include the processing rate based on overall processing" do
|
242
|
+
@states.size.should == 2
|
243
|
+
# after 10 triggers we've taken 109 seconds - pretty slow
|
244
|
+
@states[0].long_rate.should be_close((10.0/109), 0.001)
|
245
|
+
# after 20 triggers it's picked up, but not a lot:
|
246
|
+
@states[1].long_rate.should be_close((20.0/119), 0.001)
|
247
|
+
end
|
248
|
+
it "should include the processing rate based on single-step processing" do
|
249
|
+
@states.size.should == 2
|
250
|
+
# after 10 triggers we've taken 109 seconds - pretty slow
|
251
|
+
@states[0].short_rate.should be_close((10.0/109), 0.001)
|
252
|
+
# after 20 triggers we can ignore the slow start time = now chugging along at 1 per second
|
253
|
+
@states[1].short_rate.should be_close(1, 0.01)
|
254
|
+
end
|
255
|
+
it "should calculate an ETA based on overall processing" do
|
256
|
+
@states.size.should == 2
|
257
|
+
# after 10 triggers we've taken 109 seconds - pretty slow - should take us 1090 seconds for the lot
|
258
|
+
@states[0].long_eta.should be_close(1090 - 109, 0.1)
|
259
|
+
# after 20 triggers it's picked up, we've taken 119 seconds - should take us (119/20)*80 seconds to finish
|
260
|
+
@states[1].long_eta.should be_close((119.0/20)*80, 0.1)
|
261
|
+
end
|
262
|
+
it "should calculate an ETA based on single-step processing" do
|
263
|
+
@states.size.should == 2
|
264
|
+
# after 10 triggers we've taken 109 seconds - same as the long-term situation - should take us 1090 seconds for the lot
|
265
|
+
@states[0].short_eta.should be_close(1090 - 109, 0.1)
|
266
|
+
# after 20 triggers it's picked up, we're now averaging 1 per second - should take us 80 seconds to finish
|
267
|
+
@states[1].short_eta.should be_close(80, 0.1)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: progress-logger
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
version: 1.0.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Kornelis Sietsma
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-10-27 00:00:00 +11:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 1
|
30
|
+
- 2
|
31
|
+
- 9
|
32
|
+
version: 1.2.9
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: delorean
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
- 2
|
46
|
+
- 0
|
47
|
+
version: 0.2.0
|
48
|
+
type: :development
|
49
|
+
version_requirements: *id002
|
50
|
+
description: Simple gem for regular progress logging in a long-running loop, log every N actions, or based on a time interval
|
51
|
+
email: korny@sietsma.com
|
52
|
+
executables: []
|
53
|
+
|
54
|
+
extensions: []
|
55
|
+
|
56
|
+
extra_rdoc_files:
|
57
|
+
- LICENSE
|
58
|
+
- README.rdoc
|
59
|
+
files:
|
60
|
+
- .document
|
61
|
+
- .gitignore
|
62
|
+
- LICENSE
|
63
|
+
- README.rdoc
|
64
|
+
- Rakefile
|
65
|
+
- VERSION
|
66
|
+
- lib/progress-logger.rb
|
67
|
+
- progress-logger.gemspec
|
68
|
+
- spec/progress-logger_spec.rb
|
69
|
+
- spec/spec.opts
|
70
|
+
- spec/spec_helper.rb
|
71
|
+
has_rdoc: true
|
72
|
+
homepage: http://github.com/kornysietsma/progress-logger
|
73
|
+
licenses: []
|
74
|
+
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options:
|
77
|
+
- --charset=UTF-8
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
version: "0"
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
segments:
|
94
|
+
- 0
|
95
|
+
version: "0"
|
96
|
+
requirements: []
|
97
|
+
|
98
|
+
rubyforge_project:
|
99
|
+
rubygems_version: 1.3.7
|
100
|
+
signing_key:
|
101
|
+
specification_version: 3
|
102
|
+
summary: Simple gem for regular progress logging in a long-running loop
|
103
|
+
test_files:
|
104
|
+
- spec/progress-logger_spec.rb
|
105
|
+
- spec/spec_helper.rb
|