Skip to content

Commit 4c01c31

Browse files
committedFeb 20, 2019
Initial commit
0 parents  commit 4c01c31

11 files changed

+290
-0
lines changed
 

‎.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pkg/*
2+
coverage/*
3+
Gemfile.lock

‎.rspec

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
--color
2+
--format d

‎CODE_OF_CONDUCT.md

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Contributor Covenant Code of Conduct
2+
3+
## Our Pledge
4+
5+
In the interest of fostering an open and welcoming environment, we as
6+
contributors and maintainers pledge to making participation in our project and
7+
our community a harassment-free experience for everyone, regardless of age, body
8+
size, disability, ethnicity, gender identity and expression, level of experience,
9+
nationality, personal appearance, race, religion, or sexual identity and
10+
orientation.
11+
12+
## Our Standards
13+
14+
Examples of behavior that contributes to creating a positive environment
15+
include:
16+
17+
* Using welcoming and inclusive language
18+
* Being respectful of differing viewpoints and experiences
19+
* Gracefully accepting constructive criticism
20+
* Focusing on what is best for the community
21+
* Showing empathy towards other community members
22+
23+
Examples of unacceptable behavior by participants include:
24+
25+
* The use of sexualized language or imagery and unwelcome sexual attention or
26+
advances
27+
* Trolling, insulting/derogatory comments, and personal or political attacks
28+
* Public or private harassment
29+
* Publishing others' private information, such as a physical or electronic
30+
address, without explicit permission
31+
* Other conduct which could reasonably be considered inappropriate in a
32+
professional setting
33+
34+
## Our Responsibilities
35+
36+
Project maintainers are responsible for clarifying the standards of acceptable
37+
behavior and are expected to take appropriate and fair corrective action in
38+
response to any instances of unacceptable behavior.
39+
40+
Project maintainers have the right and responsibility to remove, edit, or
41+
reject comments, commits, code, wiki edits, issues, and other contributions
42+
that are not aligned to this Code of Conduct, or to ban temporarily or
43+
permanently any contributor for other behaviors that they deem inappropriate,
44+
threatening, offensive, or harmful.
45+
46+
## Scope
47+
48+
This Code of Conduct applies both within project spaces and in public spaces
49+
when an individual is representing the project or its community. Examples of
50+
representing a project or community include using an official project e-mail
51+
address, posting via an official social media account, or acting as an appointed
52+
representative at an online or offline event. Representation of a project may be
53+
further defined and clarified by project maintainers.
54+
55+
## Enforcement
56+
57+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
58+
reported by contacting the project team at andrey.radev@gmail.com. All
59+
complaints will be reviewed and investigated and will result in a response that
60+
is deemed necessary and appropriate to the circumstances. The project team is
61+
obligated to maintain confidentiality with regard to the reporter of an incident.
62+
Further details of specific enforcement policies may be posted separately.
63+
64+
Project maintainers who do not follow or enforce the Code of Conduct in good
65+
faith may face temporary or permanent repercussions as determined by other
66+
members of the project's leadership.
67+
68+
## Attribution
69+
70+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71+
available at [http://contributor-covenant.org/version/1/4][version]
72+
73+
[homepage]: http://contributor-covenant.org
74+
[version]: http://contributor-covenant.org/version/1/4/

‎Gemfile

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
gemspec
2+
3+
source 'https://rubygems.org'

‎LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2018 Andrew Radev
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

‎README.md

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
A very basic library to measure loops in a long-running task.
2+
3+
*Note: Very incomplete, so mostly for personal usage. Will hopefully flesh it out, write tests, configuration, etc, at some point (PRs welcome). Until then, a similar library can be found here: https://github.com/mkdynamic/ke*
4+
5+
Example usage:
6+
7+
``` ruby
8+
progressor = Progressor.new(total_count: Product.count)
9+
10+
Product.find_each do |product|
11+
if product.not_something_we_want_to_process?
12+
progressor.skip(1)
13+
next
14+
end
15+
16+
progressor.run do |progress|
17+
puts "[#{progress}] Product #{product.id}"
18+
product.calculate_interesting_stats
19+
end
20+
end
21+
```
22+
23+
Example output:
24+
25+
```
26+
...
27+
[0038/1000, (004%), t/i: 0.5s, ETA: 8m:0.27s] Product 38
28+
[0039/1000, (004%), t/i: 0.5s, ETA: 7m:58.47s] Product 39
29+
[0040/1000, (004%), t/i: 0.5s, ETA: 7m:57.08s] Product 40
30+
...
31+
```

‎Rakefile

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require 'bundler'
2+
Bundler::GemHelper.install_tasks

‎_project.vim

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
runtime projects/ruby.vim

‎lib/progressor.rb

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
require 'benchmark'
2+
3+
# Used to measure the running time of parts of a long-running task and output
4+
# an estimation based on the average of the last 10-100 measurements.
5+
#
6+
# Example usage:
7+
#
8+
# progressor = Progressor.new(total_count: Product.count)
9+
#
10+
# Product.find_each do |product|
11+
# if product.not_something_we_want_to_process?
12+
# progressor.skip(1)
13+
# next
14+
# end
15+
#
16+
# progressor.run do |progress|
17+
# puts "[#{progress}] Product #{product.id}"
18+
# product.calculate_interesting_stats
19+
# end
20+
# end
21+
#
22+
# Example output:
23+
#
24+
# ...
25+
# [0038/1000, (004%), t/i: 0.5s, ETA: 8m:0.27s] Product 38
26+
# [0039/1000, (004%), t/i: 0.5s, ETA: 7m:58.47s] Product 39
27+
# [0040/1000, (004%), t/i: 0.5s, ETA: 7m:57.08s] Product 40
28+
# ...
29+
#
30+
class Progressor
31+
VERSION = '0.0.1'
32+
33+
# Utility method to print a message with the time it took to run the contents
34+
# of the block.
35+
#
36+
# > Progressor.puts("Working on a thing") { thing_work }
37+
#
38+
# Working on a thing...
39+
# Working on a thing DONE: 2.1s
40+
#
41+
def self.puts(message, &block)
42+
Kernel.puts "#{message}..."
43+
measurement = Benchmark.measure { block.call }
44+
Kernel.puts "#{message} DONE: #{format_time(measurement.real)}"
45+
end
46+
47+
def initialize(total_count:)
48+
@total_count = total_count
49+
@total_count_digits = total_count.to_s.length
50+
@current = 0
51+
@measurements = []
52+
@averages = []
53+
end
54+
55+
def run
56+
@current += 1
57+
58+
measurement = Benchmark.measure { yield self }
59+
60+
@measurements << measurement.real
61+
# only keep last 1000
62+
@measurements.shift if @measurements.count > 1000
63+
64+
@averages << average(@measurements)
65+
@averages = @averages.compact
66+
# only keep last 100
67+
@averages.shift if @averages.count > 100
68+
end
69+
70+
def skip(n)
71+
@total_count -= n
72+
end
73+
74+
def to_s
75+
[
76+
"#{@current.to_s.rjust(@total_count_digits, '0')}/#{@total_count}",
77+
"(#{((@current / @total_count.to_f) * 100).round.to_s.rjust(3, '0')}%)",
78+
"t/i: #{self.class.format_time(per_iteration)}",
79+
"ETA: #{self.class.format_time(eta)}",
80+
].join(', ')
81+
end
82+
83+
def per_iteration
84+
return nil if @measurements.count < 10
85+
average(@averages)
86+
end
87+
88+
def eta
89+
return nil if @measurements.count < 10
90+
91+
remaining_time = per_iteration * (@total_count - @current)
92+
remaining_time.round(2)
93+
end
94+
95+
private
96+
97+
def self.format_time(time)
98+
return "?s" if time.nil?
99+
100+
if time < 0.1
101+
"#{(time * 1000).round(2)}ms"
102+
elsif time < 60
103+
"#{time.round(2)}s"
104+
elsif time < 3600
105+
minutes = time.to_i / 60
106+
seconds = (time - minutes * 60).round(2)
107+
"#{minutes}m:#{seconds}s"
108+
else
109+
hours = time.to_i / 3600
110+
minutes = (time.to_i % 3600) / 60
111+
seconds = (time - (hours * 3600 + minutes * 60)).round(2)
112+
"#{hours}h:#{minutes}m:#{seconds}s"
113+
end
114+
end
115+
116+
def average(collection)
117+
collection.inject(&:+) / collection.count.to_f
118+
end
119+
end

‎progressor.gemspec

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
require File.expand_path('../lib/progressor', __FILE__)
2+
3+
Gem::Specification.new do |s|
4+
s.name = 'progressor'
5+
s.version = Progressor::VERSION
6+
s.authors = ['Andrew Radev']
7+
s.email = ['andrey.radev@gmail.com']
8+
9+
s.homepage = 'https://github.com/AndrewRadev/progressor'
10+
s.license = 'MIT'
11+
s.summary = 'Measure iterations in a long-running task'
12+
s.description = <<~EOF
13+
Provides a way to measure how long each loop in a task took, outputting a
14+
report with an estimated time till the task is done.
15+
EOF
16+
17+
s.add_development_dependency "bundler", "~> 1.17"
18+
s.add_development_dependency "rake", "~> 10.0"
19+
s.add_development_dependency "rspec", "~> 3.0"
20+
21+
s.files = Dir['{lib}/**/*.rb', 'LICENSE', '*.md']
22+
s.require_paths = ['lib']
23+
end

‎spec/spec_helper.rb

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
require 'pp'
2+
require 'bundler/setup'
3+
4+
RSpec.configure do |config|
5+
# Enable flags like --only-failures and --next-failure
6+
config.example_status_persistence_file_path = ".rspec_status"
7+
8+
config.expect_with :rspec do |c|
9+
c.syntax = :expect
10+
end
11+
end

0 commit comments

Comments
 (0)
Please sign in to comment.