timeasure 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.rspec +1 -0
- data/.rubocop.yml +11 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +207 -0
- data/Rakefile +2 -0
- data/lib/timeasure.rb +69 -0
- data/lib/timeasure/class_methods.rb +33 -0
- data/lib/timeasure/configuration.rb +42 -0
- data/lib/timeasure/measurement.rb +26 -0
- data/lib/timeasure/profiling/manager.rb +39 -0
- data/lib/timeasure/profiling/reported_method.rb +27 -0
- data/lib/timeasure/profiling/reported_methods_handler.rb +30 -0
- data/lib/timeasure/version.rb +3 -0
- data/timeasure.gemspec +27 -0
- metadata +121 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 284614263b2c5c4343831c8d79e391c869009490
|
4
|
+
data.tar.gz: 72aa7b99c6eaeef54890c1c12d66679be73895f1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 175a8df40f6cf4a2ef58b11375b6b83da05499d960ed67055812fae8ebb9337b5d76834a432efc781d6ca8afc43f2fe816011538047755746bffb1ca448e853d
|
7
|
+
data.tar.gz: 20b2c718f96d7d0f8057cb086dc0cdab8fce80b1933fe9b7c0f68dddfa9606244b99cf57311b89976bac98250c8bd15652f832aba2ceb4317cd2e7d7c29dc5e5
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
.idea/*
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/.rubocop.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2017 Eliav Lavi
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
# Timeasure
|
2
|
+
|
3
|
+
**What Is It?**
|
4
|
+
|
5
|
+
Timeasure is a transparent method-level wrapper for profiling purposes developed by [Eliav Lavi](http://www.eliavlavi.com) & [Riskified](https://www.riskified.com/)).
|
6
|
+
|
7
|
+
Timeasure is a Ruby gem that allows measuring the runtime of methods in production environments
|
8
|
+
without having to alter the code of the methods themselves.
|
9
|
+
|
10
|
+
Timeasure allows you to declare tracked methods to be measured transparently upon each call.
|
11
|
+
Measured calls are then reported to Timeasure's Profiler, which aggregates the measurements on the method level.
|
12
|
+
This part is configurable and if you wish you can report measurements to another profiler of your choice.
|
13
|
+
|
14
|
+
**Why Use It?**
|
15
|
+
|
16
|
+
Timeasure was created in order to serve as an easy-to-use, self-contained framework for method-level profiling
|
17
|
+
that is safe to use in production. Testing runtime in non-production environments is helpful, but there is
|
18
|
+
great value to the knowledge gained by measuring what really goes on at real time.
|
19
|
+
|
20
|
+
**What To Do With the Data?**
|
21
|
+
|
22
|
+
The imagined usage of measured methods timing is to aggregate it along a certain transaction and report it to a live
|
23
|
+
BI service such as [NewRelic Insights](https://newrelic.com/insights) or [Keen.io](https://keen.io/);
|
24
|
+
however, different usages might prove helpful as well, such as writing the data to a database or a file.
|
25
|
+
|
26
|
+
**Disclaimers**
|
27
|
+
|
28
|
+
Timeasure uses minimal intervention in the Ruby Object Model for tracked modules and classes.
|
29
|
+
It integrates well within Rails and non-Rails apps.
|
30
|
+
|
31
|
+
Timeasure is inspired by [Metaprogramming Ruby 2](https://pragprog.com/book/ppmetr2/metaprogramming-ruby-2)
|
32
|
+
by [Paolo Perrotta](https://twitter.com/nusco)
|
33
|
+
and by [this](https://hashrocket.com/blog/posts/module-prepend-a-super-story) blog post by Hashrocket.
|
34
|
+
|
35
|
+
## Requirements
|
36
|
+
|
37
|
+
Ruby 2.0 or a later version is mandatory. (Timeasure uses `Module#prepend` introduced in Ruby 2.0.)
|
38
|
+
|
39
|
+
## Installation
|
40
|
+
|
41
|
+
Add this line to your application's Gemfile:
|
42
|
+
|
43
|
+
gem 'timeasure'
|
44
|
+
|
45
|
+
And then execute:
|
46
|
+
|
47
|
+
$ bundle
|
48
|
+
|
49
|
+
Or install it yourself as:
|
50
|
+
|
51
|
+
$ gem install timeasure
|
52
|
+
|
53
|
+
## Usage
|
54
|
+
#### 1. Include Timeasure in Modules and Classes
|
55
|
+
Simply include the Timeasure module in any class or module and declare the desired methods to track:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
class Foo
|
59
|
+
include Timeasure
|
60
|
+
tracked_class_methods :bar
|
61
|
+
tracked_instance_methods :baz, :qux
|
62
|
+
|
63
|
+
def self.bar
|
64
|
+
# some class-level stuff that can benefit from measuring runtime...
|
65
|
+
end
|
66
|
+
|
67
|
+
def baz
|
68
|
+
# some instance-level stuff that can benefit from measuring runtime...
|
69
|
+
end
|
70
|
+
|
71
|
+
def qux
|
72
|
+
# some other instance-level stuff that can benefit from measuring runtime...
|
73
|
+
end
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
#### 2. Define the Boundaries of the Tracked Transaction
|
78
|
+
**Preparing for Method Tracking**
|
79
|
+
|
80
|
+
The user is responsible for managing the final reporting and the clean-up of the aggregated data after each transation.
|
81
|
+
It is recommended to prepare the profiler at the beginning of a transaction in which tracked methods exist with
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
Timeasure::Profiling::Manager.prepare
|
85
|
+
```
|
86
|
+
and to re-prepare it again at the end of it in order to ensure a "clean slate" -
|
87
|
+
after you have handled the aggregated data in some way.
|
88
|
+
|
89
|
+
**Getting Hold of the Data**
|
90
|
+
|
91
|
+
In order to get hold of the reported methods data, use
|
92
|
+
```ruby
|
93
|
+
Timeasure::Profiling::Manager.export
|
94
|
+
````
|
95
|
+
This will return an array of `ReportedMethod`s. Each `ReportedMethod` object holds the aggregated timing data per
|
96
|
+
each tracked method call. This means that no matter how many times you call a tracked method, Timeasure's Profiler will
|
97
|
+
still hold a single `ReportedMethod` object to represent it.
|
98
|
+
|
99
|
+
`ReportedMethod` allows reading the following attributes:
|
100
|
+
* `klass_name`: Name of the class in which the tracked method resides.
|
101
|
+
* `method_name`: Name of the tracked method.
|
102
|
+
* `segment`: See [Segmented Method Tracking](#segmented-method-tracking) below.
|
103
|
+
* `metadata`: See [Carrying Metadata](#carrying-metadata) below.
|
104
|
+
* `method_path`: `klass_name` and `method_name` concatenated.
|
105
|
+
* `full_path`: Same as `method_path` unless segmentation is declared,
|
106
|
+
in which case the segment will be concatenated to the string as well. See [Segmented Method Tracking](#segmented-method-tracking) below.
|
107
|
+
* `runtime_sum`: The aggregated time it took the reported method in question to run across all calls.
|
108
|
+
* `call_count`: The times the reported method in question was called across all calls.
|
109
|
+
|
110
|
+
|
111
|
+
## Advanced Usage
|
112
|
+
|
113
|
+
#### Segmented Method Tracking
|
114
|
+
Timeasure was designed to separate regular code from its time measurement declaration.
|
115
|
+
This is achieved by Timeasure's class macros `tracked_class_methods` and `tracked_instance_methods`.
|
116
|
+
Sometimes, however, the need for additional data might arise. Imagine this method:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
class Foo
|
120
|
+
def bar(baz)
|
121
|
+
# some stuff that can benefit from measuring runtime
|
122
|
+
# yet its runtime is also highly affected by the value of baz...
|
123
|
+
end
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
127
|
+
We've seen how Timeasure makes it easy to measure the `bar` method.
|
128
|
+
However, if we wish to segment each call by the value of `baz`,
|
129
|
+
we may use Timeasure's direct interface and send this value as a **segment**:
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
class Foo
|
133
|
+
def bar(baz)
|
134
|
+
Timeasure.measure(klass_name: 'Foo', method_name: 'bar', segment: { baz: baz }) do
|
135
|
+
# the code to be measured
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
```
|
140
|
+
|
141
|
+
For such calls, Timeasure's Profiler will aggregate the data in `ReportedMethod` objects grouped by
|
142
|
+
class, method and segment.
|
143
|
+
|
144
|
+
This approach obviously violates Timeasure's idea of separating code and measurement-declaration,
|
145
|
+
but it allows for much more detailed investigations, if needed.
|
146
|
+
This will result in different `ReportedMethod` object in Timeasure's Profiler for
|
147
|
+
each combination of class, method and segment. Accordingly, such `ReportedMethod` object will include
|
148
|
+
these three elements, concatenated, as the value for `ReportedMethod#full_path`.
|
149
|
+
|
150
|
+
#### Carrying Metadata
|
151
|
+
This feature was developed in order to complement the segmented method tracking.
|
152
|
+
|
153
|
+
Sometimes carrying data with measurement that does not define a segment might be needed.
|
154
|
+
For example, assuming we save all our `ReportedMethod`s to some table called `reported_methods`,
|
155
|
+
we might want to supply a custom table name for specific measurements.
|
156
|
+
This might be achieved by using `metadata`:
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
class Foo
|
160
|
+
def bar
|
161
|
+
Timeasure.measure(klass_name: 'Foo', method_name: 'bar', metadata: { table_name: 'my_custom_table' }) do
|
162
|
+
# the code to be measured
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
Unlike Segments, Timeasure only carries the Metadata onwards.
|
169
|
+
It is up to the user to make use of this data, probably after calling `Timeasure::Profiling::Manager.export`.
|
170
|
+
|
171
|
+
## Notes
|
172
|
+
|
173
|
+
#### Compatibility with RSpec
|
174
|
+
|
175
|
+
If you run your test suite with Timeasure installed and modules, classes and methods tracked and all works fine - hurray!
|
176
|
+
However, due to the mechanics of Timeasure - namely, its usage of prepended modules - there exist a problem with
|
177
|
+
**stubbing** Timeasure-tracked method (RSpec does not support stubbing methods that appear in a prepended module).
|
178
|
+
To be accurate, that means that if you are tracking method `#foo`, you can not
|
179
|
+
declare something like `allow(bar).to receive(:foo).and_return(bar)`. Your specs will refuse to run in this case.
|
180
|
+
To solve that problem you can configure Timeasure's `enable_timeasure_proc` **not** to run under certain conditions.
|
181
|
+
|
182
|
+
If you are on Rails, add the following as a Rails initializer:
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
require 'timeasure'
|
186
|
+
|
187
|
+
Timeasure.configure do |configuration|
|
188
|
+
configuration.enable_timeasure_proc = lambda { !Rails.env.test? }
|
189
|
+
end
|
190
|
+
```
|
191
|
+
|
192
|
+
Timeasure will not come into action if the expression in the block evaluates to `false`.
|
193
|
+
By default this block evaluates to `true`.
|
194
|
+
|
195
|
+
|
196
|
+
## Feature Requests
|
197
|
+
|
198
|
+
Timeasure is open for changes and requests!
|
199
|
+
If you have an idea, a question or some need, feel free to contact me here or at eliavlavi@gmail.com.
|
200
|
+
|
201
|
+
## Contributing
|
202
|
+
|
203
|
+
1. Fork it ( https://github.com/riskified/timeasure/fork )
|
204
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
205
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
206
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
207
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/lib/timeasure.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require_relative 'timeasure/version'
|
2
|
+
require_relative 'timeasure/configuration'
|
3
|
+
require_relative 'timeasure/class_methods'
|
4
|
+
require_relative 'timeasure/measurement'
|
5
|
+
require_relative 'timeasure/profiling/manager'
|
6
|
+
|
7
|
+
module Timeasure
|
8
|
+
class << self
|
9
|
+
def configure
|
10
|
+
yield(configuration)
|
11
|
+
end
|
12
|
+
|
13
|
+
def configuration
|
14
|
+
@configuration ||= Configuration.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def included(base_class)
|
18
|
+
base_class.extend ClassMethods
|
19
|
+
|
20
|
+
instance_interceptor = const_set(instance_interceptor_name_for(base_class), interceptor_module_for(base_class))
|
21
|
+
class_interceptor = const_set(class_interceptor_name_for(base_class), interceptor_module_for(base_class))
|
22
|
+
|
23
|
+
return unless timeasure_enabled?
|
24
|
+
|
25
|
+
base_class.prepend instance_interceptor
|
26
|
+
base_class.singleton_class.prepend class_interceptor
|
27
|
+
end
|
28
|
+
|
29
|
+
def measure(klass_name: nil, method_name: nil, segment: nil, metadata: nil)
|
30
|
+
t0 = Time.now.utc
|
31
|
+
block_return_value = yield if block_given?
|
32
|
+
t1 = Time.now.utc
|
33
|
+
|
34
|
+
begin
|
35
|
+
measurement = Timeasure::Measurement.new(klass_name: klass_name.to_s, method_name: method_name.to_s,
|
36
|
+
segment: segment, metadata: metadata, t0: t0, t1: t1)
|
37
|
+
Timeasure.configuration.post_measuring_proc.call(measurement)
|
38
|
+
rescue => e
|
39
|
+
Timeasure.configuration.rescue_proc.call(e, klass_name)
|
40
|
+
end
|
41
|
+
|
42
|
+
block_return_value
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def instance_interceptor_name_for(base_class)
|
48
|
+
"#{base_class.timeasure_name}InstanceInterceptor"
|
49
|
+
end
|
50
|
+
|
51
|
+
def class_interceptor_name_for(base_class)
|
52
|
+
"#{base_class.timeasure_name}ClassInterceptor"
|
53
|
+
end
|
54
|
+
|
55
|
+
def interceptor_module_for(base_class)
|
56
|
+
Module.new do
|
57
|
+
@klass_name = base_class
|
58
|
+
|
59
|
+
def self.klass_name
|
60
|
+
@klass_name
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def timeasure_enabled?
|
66
|
+
configuration.enable_timeasure_proc.call
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Timeasure
|
2
|
+
module ClassMethods
|
3
|
+
def tracked_instance_methods(*method_names)
|
4
|
+
method_names.each do |method_name|
|
5
|
+
instance_interceptor = const_get("#{timeasure_name}InstanceInterceptor")
|
6
|
+
add_method_to_interceptor(instance_interceptor, method_name)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def tracked_class_methods(*method_names)
|
11
|
+
method_names.each do |method_name|
|
12
|
+
class_interceptor = const_get("#{timeasure_name}ClassInterceptor")
|
13
|
+
add_method_to_interceptor(class_interceptor, method_name)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def timeasure_name
|
18
|
+
name.gsub('::', '_')
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def add_method_to_interceptor(interceptor, method_name)
|
24
|
+
interceptor.class_eval do
|
25
|
+
define_method method_name do |*args, &block|
|
26
|
+
Timeasure.measure(klass_name: interceptor.klass_name.to_s, method_name: method_name.to_s) do
|
27
|
+
super(*args, &block)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Timeasure
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :post_measuring_proc, :rescue_proc, :enable_timeasure_proc,
|
4
|
+
:reported_methods_handler_set_proc, :reported_methods_handler_get_proc
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@post_measuring_proc = lambda do |measurement|
|
8
|
+
# Enables the configuration of what to do with each method runtime measurement.
|
9
|
+
# By default it reports to Timeasure's Profiling Manager.
|
10
|
+
|
11
|
+
Timeasure::Profiling::Manager.report(measurement)
|
12
|
+
end
|
13
|
+
|
14
|
+
@rescue_proc = lambda do |e, klass|
|
15
|
+
# Enabled the configuration of post_measuring_proc rescue.
|
16
|
+
end
|
17
|
+
|
18
|
+
@enable_timeasure_proc = lambda do
|
19
|
+
# Enables toggling Timeasure's activation (e.g. for disabling Timeasure for RSpec).
|
20
|
+
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
@reported_methods_handler_set_proc = lambda do |reported_methods_handler|
|
25
|
+
# Enables configuring where to store the ReportedMethodsHandler instance.
|
26
|
+
# This proc will be called by Timeasure::Profiling::Manager.prepare.
|
27
|
+
# By default it stores the handler as a class instance variable (in Timeasure::Profiling::Manager)
|
28
|
+
|
29
|
+
@reported_methods_handler = reported_methods_handler
|
30
|
+
end
|
31
|
+
|
32
|
+
@reported_methods_handler_get_proc = lambda do
|
33
|
+
# Enables configuring where to fetch the ReportedMethodsHandler instance.
|
34
|
+
# This proc will be called by Timeasure::Profiling::Manager.report and Timeasure::Profiling::Manager.export.
|
35
|
+
# By default it fetches the handler from the class instance variable
|
36
|
+
# (see @reported_methods_handler_set_proc).
|
37
|
+
|
38
|
+
@reported_methods_handler
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Timeasure
|
2
|
+
class Measurement
|
3
|
+
attr_reader :klass_name, :method_name, :segment, :metadata, :t0, :t1
|
4
|
+
|
5
|
+
def initialize(klass_name:, method_name:, t0:, t1:, segment: nil, metadata: nil)
|
6
|
+
@klass_name = klass_name
|
7
|
+
@method_name = method_name
|
8
|
+
@t0 = t0
|
9
|
+
@t1 = t1
|
10
|
+
@segment = segment
|
11
|
+
@metadata = metadata
|
12
|
+
end
|
13
|
+
|
14
|
+
def runtime_in_milliseconds
|
15
|
+
(@t1 - @t0) * 1000
|
16
|
+
end
|
17
|
+
|
18
|
+
def full_path
|
19
|
+
@segment.nil? ? method_path : "#{method_path}:#{@segment}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def method_path
|
23
|
+
"#{@klass_name}##{@method_name}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require_relative 'reported_methods_handler'
|
3
|
+
require_relative 'reported_method'
|
4
|
+
|
5
|
+
module Timeasure
|
6
|
+
module Profiling
|
7
|
+
class Manager
|
8
|
+
class << self
|
9
|
+
def prepare
|
10
|
+
Timeasure.configuration.reported_methods_handler_set_proc.call(ReportedMethodsHandler.new)
|
11
|
+
end
|
12
|
+
|
13
|
+
def report(measurement)
|
14
|
+
handler = reported_methods_handler
|
15
|
+
handler.nil? ? warn_unprepared_handler : handler.report(measurement)
|
16
|
+
end
|
17
|
+
|
18
|
+
def export
|
19
|
+
handler = reported_methods_handler
|
20
|
+
handler.nil? ? warn_unprepared_handler : handler.export
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def reported_methods_handler
|
26
|
+
Timeasure.configuration.reported_methods_handler_get_proc.call
|
27
|
+
end
|
28
|
+
|
29
|
+
def warn_unprepared_handler
|
30
|
+
logger.warn("#{self} is not prepared. Call Timeasure::Profiling::Manager.prepare before trying to report measurements or export reported methods.")
|
31
|
+
end
|
32
|
+
|
33
|
+
def logger
|
34
|
+
@logger ||= Logger.new(STDOUT)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Timeasure
|
2
|
+
module Profiling
|
3
|
+
class ReportedMethod
|
4
|
+
attr_reader :klass_name, :method_name, :segment, :metadata, :full_path, :method_path, :runtime_sum, :call_count
|
5
|
+
|
6
|
+
def initialize(measurement)
|
7
|
+
@klass_name = measurement.klass_name
|
8
|
+
@method_name = measurement.method_name
|
9
|
+
@segment = measurement.segment
|
10
|
+
@metadata = measurement.metadata
|
11
|
+
@full_path = measurement.full_path
|
12
|
+
@method_path = measurement.method_path
|
13
|
+
|
14
|
+
@runtime_sum = 0
|
15
|
+
@call_count = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def increment_runtime_sum(runtime)
|
19
|
+
@runtime_sum += runtime
|
20
|
+
end
|
21
|
+
|
22
|
+
def increment_call_count
|
23
|
+
@call_count += 1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Timeasure
|
2
|
+
module Profiling
|
3
|
+
class ReportedMethodsHandler
|
4
|
+
def initialize
|
5
|
+
@reported_methods = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def report(measurement)
|
9
|
+
initialize_path_for(measurement) if path_uninitialized_for(measurement)
|
10
|
+
|
11
|
+
@reported_methods[measurement.full_path].increment_runtime_sum(measurement.runtime_in_milliseconds)
|
12
|
+
@reported_methods[measurement.full_path].increment_call_count
|
13
|
+
end
|
14
|
+
|
15
|
+
def export
|
16
|
+
@reported_methods.values
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def path_uninitialized_for(measurement)
|
22
|
+
@reported_methods[measurement.full_path].nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize_path_for(measurement)
|
26
|
+
@reported_methods[measurement.full_path] = ReportedMethod.new(measurement)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/timeasure.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'timeasure/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'timeasure'
|
7
|
+
spec.version = Timeasure::VERSION
|
8
|
+
spec.authors = ['Eliav Lavi']
|
9
|
+
spec.email = ['eliav@riskified.com', 'eliavlavi@gmail.com']
|
10
|
+
spec.summary = 'Transparent method-level wrapper for profiling purposes'
|
11
|
+
spec.description = <<-DESCRIPTION
|
12
|
+
Timeasure is a Ruby gem that allows measuring the runtime of methods
|
13
|
+
without having to alter the code of the methods themselves.
|
14
|
+
DESCRIPTION
|
15
|
+
spec.homepage = 'https://github.com/riskified/timeasure'
|
16
|
+
spec.license = 'MIT'
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.start_with? 'spec/' }
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^spec/})
|
20
|
+
spec.require_paths = ['lib', 'lib/timeasure', 'lib/timeasure/profiling']
|
21
|
+
|
22
|
+
spec.required_ruby_version = '>= 2.0'
|
23
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
24
|
+
spec.add_development_dependency 'coveralls'
|
25
|
+
spec.add_development_dependency 'rake', '~> 12.0'
|
26
|
+
spec.add_development_dependency 'rspec', '~> 3.6'
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: timeasure
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eliav Lavi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-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: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: coveralls
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '12.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '12.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.6'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.6'
|
69
|
+
description: |2
|
70
|
+
Timeasure is a Ruby gem that allows measuring the runtime of methods
|
71
|
+
without having to alter the code of the methods themselves.
|
72
|
+
email:
|
73
|
+
- eliav@riskified.com
|
74
|
+
- eliavlavi@gmail.com
|
75
|
+
executables: []
|
76
|
+
extensions: []
|
77
|
+
extra_rdoc_files: []
|
78
|
+
files:
|
79
|
+
- ".gitignore"
|
80
|
+
- ".rspec"
|
81
|
+
- ".rubocop.yml"
|
82
|
+
- Gemfile
|
83
|
+
- LICENSE.txt
|
84
|
+
- README.md
|
85
|
+
- Rakefile
|
86
|
+
- lib/timeasure.rb
|
87
|
+
- lib/timeasure/class_methods.rb
|
88
|
+
- lib/timeasure/configuration.rb
|
89
|
+
- lib/timeasure/measurement.rb
|
90
|
+
- lib/timeasure/profiling/manager.rb
|
91
|
+
- lib/timeasure/profiling/reported_method.rb
|
92
|
+
- lib/timeasure/profiling/reported_methods_handler.rb
|
93
|
+
- lib/timeasure/version.rb
|
94
|
+
- timeasure.gemspec
|
95
|
+
homepage: https://github.com/riskified/timeasure
|
96
|
+
licenses:
|
97
|
+
- MIT
|
98
|
+
metadata: {}
|
99
|
+
post_install_message:
|
100
|
+
rdoc_options: []
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
- lib/timeasure
|
104
|
+
- lib/timeasure/profiling
|
105
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '2.0'
|
110
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
requirements: []
|
116
|
+
rubyforge_project:
|
117
|
+
rubygems_version: 2.6.14
|
118
|
+
signing_key:
|
119
|
+
specification_version: 4
|
120
|
+
summary: Transparent method-level wrapper for profiling purposes
|
121
|
+
test_files: []
|