multi_measure 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 85c1f833335aceab867050b604697ddddc6ea51553b008c93c204a17edb8d5a5
4
+ data.tar.gz: dcd90263d5a7c4893b3f22f708dad1c03e249d7a93e129bf7c2cc050dcf17df8
5
+ SHA512:
6
+ metadata.gz: 7b92a03ac6a094c2136aa61e4c63ce6e9f2572f24c5a1880e2e0f037521cd0fd7533c017ed366d94602d2785816b1778d35535993959c3f0a2d495553d0c4d3b
7
+ data.tar.gz: 1f195cab27258836d41bd379126b706269aa01f39cbf2580443deb20c35efd11a255fce5a7c2a711939fa96dce45d71733a234854a16bcfe9bff6feba60f7d31
@@ -0,0 +1,20 @@
1
+ name: Ruby
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ build:
7
+
8
+ runs-on: ubuntu-latest
9
+
10
+ steps:
11
+ - uses: actions/checkout@v2
12
+ - name: Set up Ruby 2.6
13
+ uses: actions/setup-ruby@v1
14
+ with:
15
+ ruby-version: 2.6.x
16
+ - name: Build and test with Rake
17
+ run: |
18
+ gem install bundler
19
+ bundle install --jobs 4 --retry 3
20
+ bundle exec rake
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.5.0
5
+ before_install: gem install bundler -v 1.16.1
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in multi_measure.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,36 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ multi_measure (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ activesupport (5.2.0)
10
+ concurrent-ruby (~> 1.0, >= 1.0.2)
11
+ i18n (>= 0.7, < 2)
12
+ minitest (~> 5.1)
13
+ tzinfo (~> 1.1)
14
+ concurrent-ruby (1.0.5)
15
+ i18n (1.0.1)
16
+ concurrent-ruby (~> 1.0)
17
+ minitest (5.11.3)
18
+ rake (10.5.0)
19
+ takes_macro (1.0.0)
20
+ thread_safe (0.3.6)
21
+ tzinfo (1.2.5)
22
+ thread_safe (~> 0.1)
23
+
24
+ PLATFORMS
25
+ ruby
26
+
27
+ DEPENDENCIES
28
+ activesupport (>= 4.0.0)
29
+ bundler (~> 2.1.0)
30
+ minitest (~> 5.0)
31
+ multi_measure!
32
+ rake (~> 10.0)
33
+ takes_macro (~> 1.0)
34
+
35
+ BUNDLED WITH
36
+ 2.1.0
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # MultiMeasure
2
+
3
+ ![ci badge](https://github.com/tonsser/multi_measure/workflows/Ruby/badge.svg "ci badge")
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "multi_measure"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,147 @@
1
+ require "multi_measure/version"
2
+ require "multi_measure/thread_safe_hash"
3
+ require "multi_measure/math_helpers"
4
+ require "multi_measure/null_measurer"
5
+ require "multi_measure/middleware"
6
+ require "multi_measure/macros"
7
+ require "multi_measure/measurement_state"
8
+ require "benchmark"
9
+ require "multi_measure/config"
10
+
11
+ class MultiMeasure
12
+ class << self
13
+ def configure
14
+ yield config
15
+ @config = config
16
+ end
17
+
18
+ def default_config
19
+ MultiMeasure::Config.build_from(
20
+ output_prefix: "MULTI_MEASURE_TOTAL",
21
+ write_to: STDOUT,
22
+ middleware: {
23
+ env_var: "MULTI_MEASURE_ENABLED"
24
+ }
25
+ )
26
+ end
27
+
28
+ def set_default_config
29
+ @config = default_config
30
+ end
31
+
32
+ def config
33
+ @config ||= default_config
34
+ end
35
+
36
+ def start
37
+ Thread.current[STORE_KEY] = new
38
+ end
39
+
40
+ def reset
41
+ Thread.current[STORE_KEY] = nil
42
+ end
43
+
44
+ def done
45
+ instance.print_all_measurements
46
+ reset
47
+ end
48
+
49
+ def track_measurements
50
+ start
51
+ yield.tap { done }
52
+ end
53
+
54
+ def current_measurements
55
+ instance.current_measurements
56
+ end
57
+
58
+ def measure(*names, &block)
59
+ instance.measure(*names, &block)
60
+ end
61
+
62
+ private
63
+
64
+ STORE_KEY = :multi_measure_thread
65
+
66
+ def instance
67
+ Thread.current.fetch(STORE_KEY) { NullMeasurer.new }
68
+ end
69
+ end
70
+
71
+ def initialize
72
+ @measurements = ThreadSafeHash.new
73
+ end
74
+
75
+ def measure(*names)
76
+ return_value = nil
77
+
78
+ state = MeasurementState.new
79
+
80
+ time = Benchmark.realtime do
81
+ return_value = yield state
82
+ end
83
+
84
+ if state.track_measurement?
85
+ names.each do |name|
86
+ measurements[name] ||= []
87
+ measurements[name] << time
88
+ end
89
+ end
90
+
91
+ return_value
92
+ end
93
+
94
+ def current_measurements
95
+ measurements.to_normal_hash
96
+ end
97
+
98
+ def print_all_measurements
99
+ longest_key_length = 0
100
+ measurements.each do |key, _value|
101
+ if key.length > longest_key_length
102
+ longest_key_length = key.length
103
+ end
104
+ end
105
+
106
+ longest_time_length = 0
107
+ measurements.each do |_key, times|
108
+ length = to_ms(times.sum).to_s.length
109
+ if length > longest_time_length
110
+ longest_time_length = length
111
+ end
112
+ end
113
+
114
+ sorted_measurements = measurements.sort_by { |_key, times| times.sum }
115
+ sorted_measurements.each do |key, times|
116
+ key = key.rjust(longest_key_length)
117
+ time = to_ms(times.sum).to_s.rjust(longest_time_length)
118
+
119
+ std = MathHelpers.standard_deviation(times.map { |t| to_ms(t) }).round(2)
120
+ extra_info = "(+/- #{std}ms, #{times.size} calls)"
121
+
122
+ write_to_io "#{key} #{time}ms #{extra_info}"
123
+ end
124
+ end
125
+
126
+ private
127
+
128
+ attr_reader :measurements
129
+
130
+ def write_to_io(message)
131
+ message = "#{config.output_prefix} #{message}"
132
+
133
+ if config.write_to.respond_to?(:debug)
134
+ config.write_to.debug(message)
135
+ else
136
+ config.write_to.puts(message)
137
+ end
138
+ end
139
+
140
+ def config
141
+ self.class.config
142
+ end
143
+
144
+ def to_ms(seconds)
145
+ (seconds * 1_000).round(1)
146
+ end
147
+ end
@@ -0,0 +1,22 @@
1
+ require "active_support/ordered_options"
2
+
3
+ class MultiMeasure
4
+ module Config
5
+ class << self
6
+ def build_from(hash)
7
+ config = ActiveSupport::OrderedOptions.new
8
+
9
+ hash.each do |key, value|
10
+ case value
11
+ when Hash
12
+ config[key] = build_from(value)
13
+ else
14
+ config[key] = value
15
+ end
16
+ end
17
+
18
+ config
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,26 @@
1
+ require "active_support/concern"
2
+
3
+ class MultiMeasure
4
+ module Macros
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def multi_measure(*methods)
9
+ methods.each do |method|
10
+ method = method.to_s
11
+
12
+ aliased_name = "__multi_measure_original_#{method}__"
13
+ alias_method aliased_name, method
14
+
15
+ measure_key = "#{self.name}##{method}"
16
+
17
+ define_method(method) do |*args|
18
+ MultiMeasure.measure(measure_key) do
19
+ send(aliased_name, *args)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ class MultiMeasure
2
+ # Copied from https://stackoverflow.com/questions/7749568/how-can-i-do-standard-deviation-in-ruby
3
+ module MathHelpers
4
+ def self.sum(a)
5
+ a.inject(0){ |accum, i| accum + i }
6
+ end
7
+
8
+ def self.mean(a)
9
+ sum(a) / a.length.to_f
10
+ end
11
+
12
+ def self.sample_variance(a)
13
+ m = mean(a)
14
+ sum = a.inject(0){ |accum, i| accum + (i - m) ** 2 }
15
+ sum / (a.length - 1).to_f
16
+ end
17
+
18
+ def self.standard_deviation(a)
19
+ return 0.0 if a.length < 2
20
+ Math.sqrt(sample_variance(a))
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ class MultiMeasure
2
+ class MeasurementState
3
+ def initialize
4
+ @track_measurement = true
5
+ end
6
+
7
+ def ignore_measurement
8
+ @track_measurement = false
9
+ end
10
+
11
+ def track_measurement?
12
+ @track_measurement
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ class MultiMeasure
2
+ class Middleware
3
+ def self.enabled?
4
+ ENV[MultiMeasure.config.middleware.env_var] != "false"
5
+ end
6
+
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ if self.class.enabled?
13
+ MultiMeasure.track_measurements do
14
+ MultiMeasure.measure("request") { @app.call(env) }
15
+ end
16
+ else
17
+ @app.call(env)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ class MultiMeasure
2
+ class NullMeasurer
3
+ def measure(*)
4
+ yield
5
+ end
6
+
7
+ def current_measurements(*)
8
+ {}
9
+ end
10
+
11
+ def print_all_measurements(*); end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ class MultiMeasure
2
+ class ThreadSafeHash
3
+ def initialize
4
+ @mutex = Mutex.new
5
+ @hash = {}
6
+ end
7
+
8
+ def [](key)
9
+ @mutex.synchronize { @hash[key] }
10
+ end
11
+
12
+ def []=(key, value)
13
+ @mutex.synchronize { @hash[key] = value }
14
+ end
15
+
16
+ include Enumerable
17
+
18
+ def each(&block)
19
+ @mutex.synchronize { @hash.each(&block) }
20
+ end
21
+
22
+ def to_normal_hash
23
+ @mutex.synchronize { @hash.dup }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ class MultiMeasure
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,29 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "multi_measure/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "multi_measure"
8
+ spec.version = MultiMeasure::VERSION
9
+ spec.authors = ["David Pedersen"]
10
+ spec.email = ["david.pdrsn@gmail.com"]
11
+ spec.licenses = ['Nonstandard']
12
+
13
+ spec.summary = %q{Useful for measuring blocks of code execution time.}
14
+ spec.description = %q{Useful for measuring blocks of code execution time.}
15
+ spec.homepage = "http://github.com/tonsser/multi_measure"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_development_dependency "bundler", "~> 2.1.0"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "minitest", "~> 5.0"
27
+ spec.add_development_dependency "takes_macro", "~> 1.0"
28
+ spec.add_development_dependency 'activesupport', '~> 4.0', '>= 4.0.0'
29
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: multi_measure
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - David Pedersen
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-02-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 2.1.0
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 2.1.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: takes_macro
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activesupport
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 4.0.0
76
+ - - "~>"
77
+ - !ruby/object:Gem::Version
78
+ version: '4.0'
79
+ type: :development
80
+ prerelease: false
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: 4.0.0
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '4.0'
89
+ description: Useful for measuring blocks of code execution time.
90
+ email:
91
+ - david.pdrsn@gmail.com
92
+ executables: []
93
+ extensions: []
94
+ extra_rdoc_files: []
95
+ files:
96
+ - ".github/workflows/ruby.yml"
97
+ - ".gitignore"
98
+ - ".travis.yml"
99
+ - Gemfile
100
+ - Gemfile.lock
101
+ - README.md
102
+ - Rakefile
103
+ - bin/console
104
+ - bin/setup
105
+ - lib/multi_measure.rb
106
+ - lib/multi_measure/config.rb
107
+ - lib/multi_measure/macros.rb
108
+ - lib/multi_measure/math_helpers.rb
109
+ - lib/multi_measure/measurement_state.rb
110
+ - lib/multi_measure/middleware.rb
111
+ - lib/multi_measure/null_measurer.rb
112
+ - lib/multi_measure/thread_safe_hash.rb
113
+ - lib/multi_measure/version.rb
114
+ - multi_measure.gemspec
115
+ homepage: http://github.com/tonsser/multi_measure
116
+ licenses:
117
+ - Nonstandard
118
+ metadata: {}
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubygems_version: 3.0.3
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: Useful for measuring blocks of code execution time.
138
+ test_files: []