progress-logger 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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