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.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,24 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## RubyMine
17
+ .idea
18
+
19
+ ## PROJECT::GENERAL
20
+ coverage
21
+ rdoc
22
+ pkg
23
+
24
+ ## PROJECT::SPECIFIC
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.
@@ -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.
@@ -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
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,10 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'progress-logger'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+ require 'delorean'
7
+
8
+ Spec::Runner.configure do |config|
9
+
10
+ end
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