class-profiler 0.1.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.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +27 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +38 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +157 -0
- data/Guardfile +18 -0
- data/LICENSE.txt +21 -0
- data/README.md +173 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/class-profiler.gemspec +39 -0
- data/config.yml +18 -0
- data/lib/class_profiler/logging.rb +100 -0
- data/lib/class_profiler/memory.rb +78 -0
- data/lib/class_profiler/methods.rb +105 -0
- data/lib/class_profiler/performance.rb +84 -0
- data/lib/class_profiler.rb +38 -0
- data/rakefile.rb +27 -0
- metadata +188 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3cc114dfe22c12351314f0374fec2f6caa62c781ec643b8ee4b9f8df53d38bb0
|
4
|
+
data.tar.gz: bd6c43d1ce630abf9389a0cc4b4048cc0b09a26e263e9bf6fc240b55de038894
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 132477ca1e6db7f33c42833fac920bb175f1725701932f9b0991ca9895d1f1f176b79a754277c302d81a61da442807ed0c985575471654d5f720f19f0dd538ad
|
7
|
+
data.tar.gz: bd324e3ea404c8256bc1c8e7192d3484e4265fa4f1e2a447e727eedad1a205aeff30729bd8d300570d0f81aac48cccf4156705c3ec2940eaf9801a7130080fba
|
@@ -0,0 +1,27 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- main
|
7
|
+
|
8
|
+
pull_request:
|
9
|
+
|
10
|
+
jobs:
|
11
|
+
build:
|
12
|
+
runs-on: ubuntu-latest
|
13
|
+
name: Ruby ${{ matrix.ruby }}
|
14
|
+
strategy:
|
15
|
+
matrix:
|
16
|
+
ruby:
|
17
|
+
- '3.2.1'
|
18
|
+
|
19
|
+
steps:
|
20
|
+
- uses: actions/checkout@v4
|
21
|
+
- name: Set up Ruby
|
22
|
+
uses: ruby/setup-ruby@v1
|
23
|
+
with:
|
24
|
+
ruby-version: ${{ matrix.ruby }}
|
25
|
+
bundler-cache: true
|
26
|
+
- name: Run the default task
|
27
|
+
run: bundle exec rake
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 3.1
|
3
|
+
|
4
|
+
Style/StringLiterals:
|
5
|
+
EnforcedStyle: single_quotes
|
6
|
+
|
7
|
+
Style/StringLiteralsInInterpolation:
|
8
|
+
EnforcedStyle: single_quotes
|
9
|
+
|
10
|
+
Style/Documentation:
|
11
|
+
Enabled: false
|
12
|
+
|
13
|
+
Metrics/BlockLength:
|
14
|
+
Exclude:
|
15
|
+
- spec/**/*.rb
|
16
|
+
- ./* # for Guardfile, gemspec, etc.
|
17
|
+
|
18
|
+
Style/BlockComments:
|
19
|
+
Enabled: false
|
20
|
+
|
21
|
+
Metrics/AbcSize:
|
22
|
+
Enabled: false
|
23
|
+
|
24
|
+
Metrics/MethodLength:
|
25
|
+
Enabled: false
|
26
|
+
|
27
|
+
Metrics/CyclomaticComplexity:
|
28
|
+
Enabled: false
|
29
|
+
|
30
|
+
Layout/LineLength:
|
31
|
+
Enabled: false
|
32
|
+
|
33
|
+
Style/AccessModifierDeclarations:
|
34
|
+
EnforcedStyle: inline
|
35
|
+
Enabled: true
|
36
|
+
|
37
|
+
Metrics/PerceivedComplexity:
|
38
|
+
Enabled: false
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
class-profiler (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
activesupport (8.0.2.1)
|
10
|
+
base64
|
11
|
+
benchmark (>= 0.3)
|
12
|
+
bigdecimal
|
13
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
14
|
+
connection_pool (>= 2.2.5)
|
15
|
+
drb
|
16
|
+
i18n (>= 1.6, < 2)
|
17
|
+
logger (>= 1.4.2)
|
18
|
+
minitest (>= 5.1)
|
19
|
+
securerandom (>= 0.3)
|
20
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
21
|
+
uri (>= 0.13.1)
|
22
|
+
ast (2.4.2)
|
23
|
+
base64 (0.3.0)
|
24
|
+
benchmark (0.4.0)
|
25
|
+
bigdecimal (3.2.2)
|
26
|
+
binding_of_caller (1.0.1)
|
27
|
+
debug_inspector (>= 1.2.0)
|
28
|
+
byebug (11.1.3)
|
29
|
+
coderay (1.1.3)
|
30
|
+
concurrent-ruby (1.3.5)
|
31
|
+
connection_pool (2.4.1)
|
32
|
+
debug_inspector (1.2.0)
|
33
|
+
diff-lcs (1.5.1)
|
34
|
+
drb (2.2.1)
|
35
|
+
ffi (1.17.2-aarch64-linux-gnu)
|
36
|
+
ffi (1.17.2-arm-linux-gnu)
|
37
|
+
ffi (1.17.2-arm64-darwin)
|
38
|
+
ffi (1.17.2-x86-linux-gnu)
|
39
|
+
ffi (1.17.2-x86_64-darwin)
|
40
|
+
ffi (1.17.2-x86_64-linux-gnu)
|
41
|
+
formatador (1.2.0)
|
42
|
+
reline
|
43
|
+
guard (2.19.1)
|
44
|
+
formatador (>= 0.2.4)
|
45
|
+
listen (>= 2.7, < 4.0)
|
46
|
+
logger (~> 1.6)
|
47
|
+
lumberjack (>= 1.0.12, < 2.0)
|
48
|
+
nenv (~> 0.1)
|
49
|
+
notiffany (~> 0.0)
|
50
|
+
ostruct (~> 0.6)
|
51
|
+
pry (>= 0.13.0)
|
52
|
+
shellany (~> 0.0)
|
53
|
+
thor (>= 0.18.1)
|
54
|
+
guard-compat (1.2.1)
|
55
|
+
guard-rspec (4.7.3)
|
56
|
+
guard (~> 2.1)
|
57
|
+
guard-compat (~> 1.1)
|
58
|
+
rspec (>= 2.99.0, < 4.0)
|
59
|
+
i18n (1.14.7)
|
60
|
+
concurrent-ruby (~> 1.0)
|
61
|
+
io-console (0.8.1)
|
62
|
+
json (2.10.1)
|
63
|
+
language_server-protocol (3.17.0.4)
|
64
|
+
lint_roller (1.1.0)
|
65
|
+
listen (3.9.0)
|
66
|
+
rb-fsevent (~> 0.10, >= 0.10.3)
|
67
|
+
rb-inotify (~> 0.9, >= 0.9.10)
|
68
|
+
logger (1.7.0)
|
69
|
+
lumberjack (1.4.1)
|
70
|
+
method_source (1.1.0)
|
71
|
+
minitest (5.25.5)
|
72
|
+
nenv (0.3.0)
|
73
|
+
notiffany (0.1.3)
|
74
|
+
nenv (~> 0.1)
|
75
|
+
shellany (~> 0.0)
|
76
|
+
ostruct (0.6.3)
|
77
|
+
parallel (1.26.3)
|
78
|
+
parser (3.3.7.1)
|
79
|
+
ast (~> 2.4.1)
|
80
|
+
racc
|
81
|
+
pry (0.14.2)
|
82
|
+
coderay (~> 1.1)
|
83
|
+
method_source (~> 1.0)
|
84
|
+
pry-byebug (3.10.1)
|
85
|
+
byebug (~> 11.0)
|
86
|
+
pry (>= 0.13, < 0.15)
|
87
|
+
pry-stack_explorer (0.6.1)
|
88
|
+
binding_of_caller (~> 1.0)
|
89
|
+
pry (~> 0.13)
|
90
|
+
racc (1.8.1)
|
91
|
+
rainbow (3.1.1)
|
92
|
+
rake (13.2.1)
|
93
|
+
rb-fsevent (0.11.2)
|
94
|
+
rb-inotify (0.11.1)
|
95
|
+
ffi (~> 1.0)
|
96
|
+
regexp_parser (2.10.0)
|
97
|
+
reline (0.6.2)
|
98
|
+
io-console (~> 0.5)
|
99
|
+
rspec (3.13.0)
|
100
|
+
rspec-core (~> 3.13.0)
|
101
|
+
rspec-expectations (~> 3.13.0)
|
102
|
+
rspec-mocks (~> 3.13.0)
|
103
|
+
rspec-core (3.13.0)
|
104
|
+
rspec-support (~> 3.13.0)
|
105
|
+
rspec-expectations (3.13.2)
|
106
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
107
|
+
rspec-support (~> 3.13.0)
|
108
|
+
rspec-mocks (3.13.1)
|
109
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
110
|
+
rspec-support (~> 3.13.0)
|
111
|
+
rspec-support (3.13.1)
|
112
|
+
rubocop (1.73.1)
|
113
|
+
json (~> 2.3)
|
114
|
+
language_server-protocol (~> 3.17.0.2)
|
115
|
+
lint_roller (~> 1.1.0)
|
116
|
+
parallel (~> 1.10)
|
117
|
+
parser (>= 3.3.0.2)
|
118
|
+
rainbow (>= 2.2.2, < 4.0)
|
119
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
120
|
+
rubocop-ast (>= 1.38.0, < 2.0)
|
121
|
+
ruby-progressbar (~> 1.7)
|
122
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
123
|
+
rubocop-ast (1.38.1)
|
124
|
+
parser (>= 3.3.1.0)
|
125
|
+
ruby-progressbar (1.13.0)
|
126
|
+
securerandom (0.4.1)
|
127
|
+
shellany (0.0.1)
|
128
|
+
thor (1.4.0)
|
129
|
+
tzinfo (2.0.6)
|
130
|
+
concurrent-ruby (~> 1.0)
|
131
|
+
unicode-display_width (3.1.4)
|
132
|
+
unicode-emoji (~> 4.0, >= 4.0.4)
|
133
|
+
unicode-emoji (4.0.4)
|
134
|
+
uri (1.0.2)
|
135
|
+
|
136
|
+
PLATFORMS
|
137
|
+
aarch64-linux
|
138
|
+
arm-linux
|
139
|
+
arm64-darwin
|
140
|
+
x86-linux
|
141
|
+
x86_64-darwin
|
142
|
+
x86_64-linux
|
143
|
+
|
144
|
+
DEPENDENCIES
|
145
|
+
activesupport
|
146
|
+
class-profiler!
|
147
|
+
guard
|
148
|
+
guard-rspec
|
149
|
+
pry
|
150
|
+
pry-byebug
|
151
|
+
pry-stack_explorer
|
152
|
+
rake
|
153
|
+
rspec
|
154
|
+
rubocop
|
155
|
+
|
156
|
+
BUNDLED WITH
|
157
|
+
2.5.17
|
data/Guardfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
guard :rspec, cmd: 'bundle exec rspec' do
|
4
|
+
require 'guard/rspec/dsl'
|
5
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
6
|
+
|
7
|
+
# Feel free to open issues for suggestions and improvements
|
8
|
+
|
9
|
+
# RSpec files
|
10
|
+
rspec = dsl.rspec
|
11
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
12
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
13
|
+
watch(rspec.spec_files)
|
14
|
+
|
15
|
+
# Ruby files
|
16
|
+
ruby = dsl.ruby
|
17
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
18
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Matthew Greenfield
|
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 all
|
13
|
+
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 THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
ClassProfiler: track performance and memory allocations in Ruby classes
|
2
|
+
|
3
|
+
### Installation
|
4
|
+
|
5
|
+
Add this line to your application's Gemfile:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
gem 'class-profiler'
|
9
|
+
```
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
```bash
|
14
|
+
bundle install
|
15
|
+
```
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
```bash
|
20
|
+
gem install class-profiler
|
21
|
+
```
|
22
|
+
|
23
|
+
### Usage
|
24
|
+
|
25
|
+
Include `ClassProfiler` in your class. Use the unified class-level helpers to wrap methods.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
class Report
|
29
|
+
include ClassProfiler
|
30
|
+
|
31
|
+
def fetch
|
32
|
+
sleep 0.01
|
33
|
+
'ok'
|
34
|
+
end
|
35
|
+
|
36
|
+
def allocate
|
37
|
+
Array.new(1000) { 'x' * 10 }
|
38
|
+
end
|
39
|
+
|
40
|
+
# Track non-inherited public/protected/private instance methods (default)
|
41
|
+
track_performance
|
42
|
+
track_memory
|
43
|
+
end
|
44
|
+
|
45
|
+
r = Report.new
|
46
|
+
r.fetch
|
47
|
+
r.allocate
|
48
|
+
|
49
|
+
# Per-method metrics
|
50
|
+
r.performance # => { fetch: { time: 0.0102, total: 0.0306 } }
|
51
|
+
r.memory # => { allocate: { allocated_objects: 1005, malloc_increase_bytes: 8192 } }
|
52
|
+
|
53
|
+
# Combined view (performance + memory)
|
54
|
+
r.profile # => { fetch: { time: 0.0102 }, allocate: { objects: 1005, bytes: 8192 } }
|
55
|
+
|
56
|
+
# Reports
|
57
|
+
r.performance_report # prints a table of last time and total time
|
58
|
+
r.memory_report # prints a table of allocations/bytes
|
59
|
+
r.profile_report # prints a combined table
|
60
|
+
```
|
61
|
+
|
62
|
+
Selection is controlled via flags on the unified helpers:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
# Include inherited methods?
|
66
|
+
track_performance inherited: true
|
67
|
+
track_memory inherited: true
|
68
|
+
|
69
|
+
# Choose visibilities (public/protected/private default to true)
|
70
|
+
track_performance protected: false, private: false
|
71
|
+
track_memory public: true, protected: false, private: false
|
72
|
+
```
|
73
|
+
|
74
|
+
### API
|
75
|
+
|
76
|
+
- `track_performance(inherited: false, public: true, protected: true, private: true)`
|
77
|
+
- `track_memory(inherited: false, public: true, protected: true, private: true)`
|
78
|
+
- `performance` (instance): Hash of method name → `{ time: Float, total: Float }`
|
79
|
+
- `memory` (instance): Hash of method name → allocation deltas
|
80
|
+
- `allocated_objects` (Integer)
|
81
|
+
- `malloc_increase_bytes` (Integer)
|
82
|
+
- `profile` (instance): Combined Hash of method name → `{ time:, objects:, bytes: }`
|
83
|
+
- `performance_report`, `memory_report`, `profile_report` (instance): print tabular reports
|
84
|
+
|
85
|
+
### Composing with wrap_method
|
86
|
+
|
87
|
+
You can compose custom behaviors using the built-in `wrap_method` helper from `ClassProfiler::Methods`.
|
88
|
+
This is the same mechanism used by the Performance and Memory modules under the hood.
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
class Widget
|
92
|
+
include ClassProfiler
|
93
|
+
|
94
|
+
def compute(x, y)
|
95
|
+
x + y
|
96
|
+
end
|
97
|
+
|
98
|
+
# Add custom logging around the original implementation
|
99
|
+
wrap_method :compute do |original, *args|
|
100
|
+
profiler_logger.info("compute called with #{args.inspect}")
|
101
|
+
result = original.bind(self).call(*args)
|
102
|
+
profiler_logger.info("compute returned #{result}")
|
103
|
+
result
|
104
|
+
end
|
105
|
+
|
106
|
+
# You can still benchmark or profile the same method
|
107
|
+
track_performance
|
108
|
+
# track_memory
|
109
|
+
end
|
110
|
+
|
111
|
+
w = Widget.new
|
112
|
+
w.compute(2, 3)
|
113
|
+
w.performance[:compute] # => { time:, total: }
|
114
|
+
```
|
115
|
+
|
116
|
+
### Requirements
|
117
|
+
|
118
|
+
- Ruby >= 3.1
|
119
|
+
|
120
|
+
### Development
|
121
|
+
|
122
|
+
- Run `bin/setup` to install dependencies
|
123
|
+
- Run `rake` to execute tests and RuboCop
|
124
|
+
- Run `bin/console` to experiment in Pry
|
125
|
+
|
126
|
+
### Examples (speed vs memory trade-offs)
|
127
|
+
|
128
|
+
Explore the gem with runnable examples that contrast time vs memory usage for common problems.
|
129
|
+
|
130
|
+
Run all examples:
|
131
|
+
|
132
|
+
```bash
|
133
|
+
bundle exec rake examples
|
134
|
+
```
|
135
|
+
|
136
|
+
What you'll see:
|
137
|
+
|
138
|
+
```text
|
139
|
+
=== Two Sum: brute_force (low memory, slower) vs hashmap (higher memory, faster) ===
|
140
|
+
Input SIZE=5000
|
141
|
+
|
142
|
+
Summary (lower time is better; lower allocations usually better):
|
143
|
+
Method Time (s) Allocated Objects Malloc +bytes
|
144
|
+
----------------------------------------------------------------------
|
145
|
+
brute_force 0.456789 12345 0
|
146
|
+
hashmap 0.012345 45678 8192
|
147
|
+
|
148
|
+
Speed: hashmap is 37.00x faster than brute_force
|
149
|
+
Memory: hashmap allocates 3.70x more objects than brute_force
|
150
|
+
|
151
|
+
Raw reports:
|
152
|
+
...
|
153
|
+
```
|
154
|
+
|
155
|
+
You can run individual examples and tune sizes:
|
156
|
+
|
157
|
+
```bash
|
158
|
+
# Two Sum: brute force vs hash map
|
159
|
+
SIZE=10000 bundle exec ruby examples/two_sum.rb
|
160
|
+
|
161
|
+
# Primes: trial division vs sieve of Eratosthenes
|
162
|
+
N=50000 bundle exec ruby examples/primes.rb
|
163
|
+
```
|
164
|
+
|
165
|
+
Each example prints a concise table plus a human-readable conclusion highlighting the trade-off (e.g. "sieve is 10x faster but allocates 4x more objects").
|
166
|
+
|
167
|
+
### Contributing
|
168
|
+
|
169
|
+
Bug reports and pull requests are welcome on GitHub at `https://github.com/omgreenfield/class-profiler`.
|
170
|
+
|
171
|
+
### License
|
172
|
+
|
173
|
+
The gem is available as open source under the terms of the MIT License.
|
data/bin/console
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'class_profiler'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
require 'pry'
|
11
|
+
Pry.start
|
data/bin/setup
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/class_profiler'
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = ClassProfiler::NAME
|
7
|
+
gem.version = ClassProfiler::VERSION
|
8
|
+
gem.authors = ['Matthew Greenfield']
|
9
|
+
gem.email = ['mattgreenfield1@gmail.com']
|
10
|
+
|
11
|
+
gem.summary = 'Benchmark speed and profile memory of class methods'
|
12
|
+
gem.description = %(
|
13
|
+
Quickly benchmark execution time and profile memory allocations for specific
|
14
|
+
or all instance methods within a class. Include ClassProfiler to get
|
15
|
+
`benchmark_methods` and `profile_methods` helpers and collect results via
|
16
|
+
`benchmarked` and `profiled_memory`.
|
17
|
+
)
|
18
|
+
gem.homepage = 'https://github.com/omgreenfield/class-profiler'
|
19
|
+
gem.license = 'MIT'
|
20
|
+
|
21
|
+
gem.files = if File.exist?(File.expand_path('.git'))
|
22
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec/|examples/)}) }
|
23
|
+
else
|
24
|
+
Dir['lib/**/*.rb'] + Dir['*.md'] + Dir['LICENSE*']
|
25
|
+
end
|
26
|
+
gem.require_paths = ['lib']
|
27
|
+
|
28
|
+
gem.required_ruby_version = '>= 3.1.0'
|
29
|
+
|
30
|
+
gem.add_development_dependency 'activesupport'
|
31
|
+
gem.add_development_dependency 'guard'
|
32
|
+
gem.add_development_dependency 'guard-rspec'
|
33
|
+
gem.add_development_dependency 'pry'
|
34
|
+
gem.add_development_dependency 'pry-byebug'
|
35
|
+
gem.add_development_dependency 'pry-stack_explorer'
|
36
|
+
gem.add_development_dependency 'rake'
|
37
|
+
gem.add_development_dependency 'rspec'
|
38
|
+
gem.add_development_dependency 'rubocop'
|
39
|
+
end
|
data/config.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
---
|
2
|
+
version: 0.1.0
|
3
|
+
name: class-profiler
|
4
|
+
authors:
|
5
|
+
- Matthew Greenfield
|
6
|
+
emails:
|
7
|
+
- mattgreenfield1@gmail.com
|
8
|
+
summary: Quickly benchmark speed and profile memory of specific or all methods within a class
|
9
|
+
description: |
|
10
|
+
Quickly benchmark execution time and profile memory allocations for specific or
|
11
|
+
all instance methods within a class. Include ClassProfiler to get helper methods
|
12
|
+
and collect results via `benchmarked` and `profiled_memory`.
|
13
|
+
homepage: https://github.com/omgreenfield/class-profiler
|
14
|
+
license: MIT
|
15
|
+
required_ruby_version: '>= 3.1.0'
|
16
|
+
metadata:
|
17
|
+
homepage_uri: https://github.com/omgreenfield/class-profiler
|
18
|
+
rubygems_mfa_required: 'true'
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ClassProfiler
|
4
|
+
module Logging
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
base.include(InstanceMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module InstanceMethods
|
11
|
+
def profiler_logger
|
12
|
+
self.class.profiler_logger
|
13
|
+
end
|
14
|
+
|
15
|
+
def print_report(entries, headers: [], include_zero: true, sort_index: 0)
|
16
|
+
rows = entries.entries
|
17
|
+
rows.filter! { |(_, time)| include_zero || time.to_f > 0.0 }
|
18
|
+
rows.sort_by! do |row|
|
19
|
+
value = row[sort_index]
|
20
|
+
if value.is_a?(Float)
|
21
|
+
value.to_f
|
22
|
+
else
|
23
|
+
value.to_s
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
rows = [headers] + rows
|
28
|
+
|
29
|
+
column_widths = rows.transpose.map do |column|
|
30
|
+
column.map { |value| value.to_s.length }.max
|
31
|
+
end
|
32
|
+
|
33
|
+
rows.each do |row|
|
34
|
+
row_text = row.map.with_index do |cell, index|
|
35
|
+
cell = format('%.6f', cell) if cell.is_a?(Float)
|
36
|
+
cell.to_s.ljust(column_widths[index])
|
37
|
+
end.join(' | ')
|
38
|
+
|
39
|
+
profiler_logger.info(row_text)
|
40
|
+
end
|
41
|
+
|
42
|
+
entries
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module ClassMethods
|
47
|
+
# Simple fan-out logger that forwards to multiple underlying loggers
|
48
|
+
class MultiLogger
|
49
|
+
def initialize(*loggers)
|
50
|
+
@loggers = loggers.compact
|
51
|
+
end
|
52
|
+
|
53
|
+
def add(severity, message = nil, progname = nil)
|
54
|
+
@loggers.each { |logger| logger.add(severity, message, progname) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def level=(level)
|
58
|
+
@loggers.each { |logger| logger.level = level }
|
59
|
+
end
|
60
|
+
|
61
|
+
def level
|
62
|
+
@loggers.map(&:level).min || Logger::INFO
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def profiler_logger
|
67
|
+
@profiler_logger ||= begin
|
68
|
+
require 'logger'
|
69
|
+
logger = Logger.new($stdout)
|
70
|
+
logger.level = Logger::INFO
|
71
|
+
logger
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def profiler_logger=(logger)
|
76
|
+
@profiler_logger = logger
|
77
|
+
end
|
78
|
+
|
79
|
+
# Configure logging to stdout
|
80
|
+
def enable_profiler_logging_to_stdout(level: Logger::INFO)
|
81
|
+
require 'logger'
|
82
|
+
self.profiler_logger = Logger.new($stdout).tap { |l| l.level = level }
|
83
|
+
end
|
84
|
+
|
85
|
+
# Configure logging to a file, optionally also to stdout
|
86
|
+
def enable_profiler_logging_to_file(path, level: Logger::INFO, shift_age: 0, shift_size: 1_048_576, also_stdout: false)
|
87
|
+
require 'logger'
|
88
|
+
file_logger = Logger.new(path, shift_age, shift_size)
|
89
|
+
file_logger.level = level
|
90
|
+
self.profiler_logger = if also_stdout
|
91
|
+
stdout_logger = Logger.new($stdout)
|
92
|
+
stdout_logger.level = level
|
93
|
+
MultiLogger.new(file_logger, stdout_logger)
|
94
|
+
else
|
95
|
+
file_logger
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ClassProfiler
|
4
|
+
module Memory
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
base.extend(Methods::ClassMethods)
|
8
|
+
base.include(InstanceMethods)
|
9
|
+
base.include(ClassProfiler::Logging)
|
10
|
+
end
|
11
|
+
|
12
|
+
module InstanceMethods
|
13
|
+
# Stores per-method memory results
|
14
|
+
# { method_name => { allocated_objects: Integer, malloc_increase_bytes: Integer } }
|
15
|
+
def memory
|
16
|
+
@memory ||= {}
|
17
|
+
end
|
18
|
+
|
19
|
+
# Prints report of each method's allocated objects and bytes used
|
20
|
+
#
|
21
|
+
# @param include_zero [Boolean] include rows with zero deltas (default: true)
|
22
|
+
# @param sort_index [Integer] 0 for method, 1 for objects, 2 for bytes
|
23
|
+
# @return [Hash] memory entries
|
24
|
+
def memory_report(include_zero: true, sort_index: 0)
|
25
|
+
headers = %w[Method Objects Bytes]
|
26
|
+
entries = memory.map { |method, values| [method] + values.values }
|
27
|
+
print_report(entries, headers: headers, include_zero:, sort_index:)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
# Unified API to select and track instance methods' memory
|
33
|
+
#
|
34
|
+
# @param inherited [Boolean] include inherited instance methods
|
35
|
+
# @param public [Boolean] include public methods
|
36
|
+
# @param protected [Boolean] include protected methods
|
37
|
+
# @param private [Boolean] include private methods
|
38
|
+
def track_memory(inherited: false, public: true, protected: true, private: true)
|
39
|
+
include_public = binding.local_variable_get(:public)
|
40
|
+
include_protected = binding.local_variable_get(:protected)
|
41
|
+
include_private = binding.local_variable_get(:private)
|
42
|
+
|
43
|
+
names = []
|
44
|
+
names |= select_instance_methods(visibility: :public, include_inherited: inherited) if include_public
|
45
|
+
names |= select_instance_methods(visibility: :protected, include_inherited: inherited) if include_protected
|
46
|
+
names |= select_instance_methods(visibility: :private, include_inherited: inherited) if include_private
|
47
|
+
measure_memory_for_methods(*names)
|
48
|
+
end
|
49
|
+
# (unified) Use track_memory to configure which methods are wrapped
|
50
|
+
|
51
|
+
# Wraps each method and records allocation deltas per call
|
52
|
+
#
|
53
|
+
# @param method_names [Array<Symbol>]
|
54
|
+
private def measure_memory_for_methods(*method_names)
|
55
|
+
require 'objspace'
|
56
|
+
|
57
|
+
method_names.each do |method_name|
|
58
|
+
wrap_method method_name do |original, *args, &block|
|
59
|
+
before_alloc_objects = GC.stat[:total_allocated_objects]
|
60
|
+
before_malloc_bytes = GC.stat[:malloc_increase_bytes] || 0
|
61
|
+
|
62
|
+
result = original.bind(self).call(*args, &block)
|
63
|
+
|
64
|
+
after_alloc_objects = GC.stat[:total_allocated_objects]
|
65
|
+
after_malloc_bytes = GC.stat[:malloc_increase_bytes] || 0
|
66
|
+
|
67
|
+
memory[method_name] = {
|
68
|
+
allocated_objects: after_alloc_objects - before_alloc_objects,
|
69
|
+
malloc_increase_bytes: after_malloc_bytes - before_malloc_bytes
|
70
|
+
}
|
71
|
+
|
72
|
+
result
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ClassProfiler
|
4
|
+
module Methods
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
base.include(InstanceMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module InstanceMethods; end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
# Helper: exclude helper methods defined by ClassProfiler modules to avoid recursion/noise
|
14
|
+
def helper_owner_module?(owner)
|
15
|
+
owner_name = owner.respond_to?(:name) ? owner.name : nil
|
16
|
+
!!owner_name&.start_with?('ClassProfiler::')
|
17
|
+
end
|
18
|
+
|
19
|
+
# Methods that should never be wrapped on instances to avoid recursion/footguns
|
20
|
+
RESERVED_INSTANCE_METHODS = %i[
|
21
|
+
instance_exec instance_eval send __send__ method public_method
|
22
|
+
respond_to? object_id __id__ class inspect to_s
|
23
|
+
].freeze
|
24
|
+
|
25
|
+
# Methods that should never be wrapped on the singleton to avoid recursion/footguns
|
26
|
+
RESERVED_SINGLETON_METHODS = %i[
|
27
|
+
send public_send method singleton_method define_singleton_method
|
28
|
+
instance_eval class_eval method_missing respond_to? allocate new superclass
|
29
|
+
inspect to_s name ancestors inherited extend include prepend
|
30
|
+
alias_method remove_method undef_method autoload autoload?
|
31
|
+
object_id __id__
|
32
|
+
].freeze
|
33
|
+
|
34
|
+
# (internal) Helper: obtain singleton class
|
35
|
+
def singleton_class_of(klass)
|
36
|
+
class << klass; self; end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Select instance method names by visibility and inheritance, excluding helpers/reserved
|
40
|
+
#
|
41
|
+
# @param visibility [Symbol] :public, :protected, :private, :all
|
42
|
+
# @param include_inherited [Boolean]
|
43
|
+
# @return [Array<Symbol>]
|
44
|
+
def select_instance_methods(visibility: :public, include_inherited: true)
|
45
|
+
names = case visibility
|
46
|
+
when :public then public_instance_methods(include_inherited)
|
47
|
+
when :protected then protected_instance_methods(include_inherited)
|
48
|
+
when :private then private_instance_methods(include_inherited)
|
49
|
+
when :all then (instance_methods(include_inherited) + protected_instance_methods(include_inherited) + private_instance_methods(include_inherited)).uniq
|
50
|
+
else public_instance_methods(include_inherited)
|
51
|
+
end
|
52
|
+
|
53
|
+
names.reject { |m| RESERVED_INSTANCE_METHODS.include?(m) || m.to_s.start_with?('_') || helper_owner_module?(instance_method(m).owner) }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Select class method names by visibility and inheritance, excluding helpers/reserved
|
57
|
+
# @param visibility [Symbol] :public, :protected, :private, :all
|
58
|
+
# @param include_inherited [Boolean]
|
59
|
+
# @return [Array<Symbol>]
|
60
|
+
def select_class_methods(visibility: :public, include_inherited: true)
|
61
|
+
singleton = singleton_class_of(self)
|
62
|
+
inherit = include_inherited || false
|
63
|
+
|
64
|
+
names = case visibility
|
65
|
+
when :public then singleton.public_instance_methods(inherit)
|
66
|
+
when :protected then singleton.protected_instance_methods(inherit)
|
67
|
+
when :private then singleton.private_instance_methods(inherit)
|
68
|
+
when :all then (singleton.instance_methods(inherit) + singleton.protected_instance_methods(inherit) + singleton.private_instance_methods(inherit)).uniq
|
69
|
+
else singleton.public_instance_methods(inherit)
|
70
|
+
end
|
71
|
+
|
72
|
+
names.reject { |m| RESERVED_SINGLETON_METHODS.include?(m) || m.to_s.start_with?('_') || helper_owner_module?(singleton.instance_method(m).owner) }
|
73
|
+
end
|
74
|
+
|
75
|
+
# Wraps a method by creating an alias to the original method creating a new
|
76
|
+
# method in its place, executing the passed in block and returning the result
|
77
|
+
# The given block is executed in the INSTANCE context via instance_exec.
|
78
|
+
#
|
79
|
+
# @yield [original_method, *args] wrapper executed in instance context
|
80
|
+
def wrap_method(method_name, *_args, prefix: '_', &wrapper)
|
81
|
+
wrapped_method_name = "#{prefix}#{method_name}".to_sym
|
82
|
+
alias_method wrapped_method_name, method_name
|
83
|
+
wrapped_method = instance_method(wrapped_method_name)
|
84
|
+
|
85
|
+
define_method(method_name) do |*args|
|
86
|
+
instance_exec(wrapped_method, *args, &wrapper)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Wrap a class method on the singleton class
|
91
|
+
def wrap_class_method(method_name, *_args, prefix: '_', &wrapper)
|
92
|
+
singleton = singleton_class_of(self)
|
93
|
+
wrapped_method_name = "#{prefix}#{method_name}".to_sym
|
94
|
+
singleton.alias_method wrapped_method_name, method_name
|
95
|
+
singleton.define_method(method_name) do |*args, &block|
|
96
|
+
start_time = Time.now
|
97
|
+
result = send(wrapped_method_name, *args, &block)
|
98
|
+
# let wrapper decide what to record/return
|
99
|
+
# wrapper executed in singleton context
|
100
|
+
wrapper.call(start_time, result, method_name)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ClassProfiler
|
4
|
+
module Performance
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
# Ensure the including class has access to wrap_method
|
8
|
+
base.extend(Methods::ClassMethods)
|
9
|
+
base.include(InstanceMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module InstanceMethods
|
13
|
+
def performance
|
14
|
+
@performance ||= {}
|
15
|
+
end
|
16
|
+
|
17
|
+
# Prints all performance-measured instance methods and the time taken per method (seconds)
|
18
|
+
#
|
19
|
+
# @param include_zero [Boolean] include methods with 0.0s measurements
|
20
|
+
# @param sort_index [Integer] 0 for method, 1 for time, 2 for total
|
21
|
+
#
|
22
|
+
# @return [Hash]
|
23
|
+
def performance_report(include_zero: false, sort_index: 1)
|
24
|
+
headers = %w[Method Time Total]
|
25
|
+
entries = performance.map do |method, values|
|
26
|
+
if values.is_a?(Hash)
|
27
|
+
[method, values[:time], values[:total]]
|
28
|
+
else
|
29
|
+
[method, values, values]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
print_report(entries, headers: headers, include_zero: include_zero, sort_index: sort_index)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module ClassMethods
|
37
|
+
include Methods::ClassMethods
|
38
|
+
|
39
|
+
# Unified API to select and track instance methods' performance
|
40
|
+
#
|
41
|
+
# @param inherited [Boolean] include inherited instance methods
|
42
|
+
# @param public [Boolean] include public methods
|
43
|
+
# @param protected [Boolean] include protected methods
|
44
|
+
# @param private [Boolean] include private methods
|
45
|
+
def track_performance(inherited: false, public: true, protected: true, private: true)
|
46
|
+
include_public = binding.local_variable_get(:public)
|
47
|
+
include_protected = binding.local_variable_get(:protected)
|
48
|
+
include_private = binding.local_variable_get(:private)
|
49
|
+
|
50
|
+
names = []
|
51
|
+
names |= select_instance_methods(visibility: :public, include_inherited: inherited) if include_public
|
52
|
+
names |= select_instance_methods(visibility: :protected, include_inherited: inherited) if include_protected
|
53
|
+
names |= select_instance_methods(visibility: :private, include_inherited: inherited) if include_private
|
54
|
+
measure_performance_for_methods(*names)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Measures non-inherited instance methods
|
58
|
+
#
|
59
|
+
# @param visibility [Symbol] :public, :protected, :private, :all
|
60
|
+
def performance_instance_methods(visibility: :public)
|
61
|
+
names = select_instance_methods(visibility: visibility, include_inherited: false)
|
62
|
+
performance_methods(*names)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Records how long each method call takes and saves it to `performance` hash
|
66
|
+
#
|
67
|
+
# @param method_names [Array<Symbol>] the names of the methods to measure
|
68
|
+
private def measure_performance_for_methods(*method_names)
|
69
|
+
method_names.each do |method_name|
|
70
|
+
wrap_method method_name do |original, *args, &block|
|
71
|
+
start_time = Time.now
|
72
|
+
result = original.bind(self).call(*args, &block)
|
73
|
+
end_time = Time.now
|
74
|
+
duration = end_time - start_time
|
75
|
+
performance[method_name] ||= { total: 0 }
|
76
|
+
performance[method_name][:total] += duration
|
77
|
+
performance[method_name][:time] = duration
|
78
|
+
result
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'class_profiler/methods'
|
4
|
+
require_relative 'class_profiler/performance'
|
5
|
+
require_relative 'class_profiler/logging'
|
6
|
+
require_relative 'class_profiler/memory'
|
7
|
+
|
8
|
+
module ClassProfiler
|
9
|
+
NAME = 'class-profiler'
|
10
|
+
VERSION = '0.1.0'
|
11
|
+
|
12
|
+
def self.included(base)
|
13
|
+
base.include(ClassProfiler::Performance)
|
14
|
+
base.include(ClassProfiler::Memory)
|
15
|
+
base.include(ClassProfiler::Logging)
|
16
|
+
base.include(InstanceMethods)
|
17
|
+
end
|
18
|
+
|
19
|
+
module InstanceMethods
|
20
|
+
def profile
|
21
|
+
perf = performance.transform_values do |value|
|
22
|
+
if value.is_a?(Hash)
|
23
|
+
{ time: value[:time] }
|
24
|
+
else
|
25
|
+
{ time: value }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
mem = memory.transform_values { |values| { objects: values[:allocated_objects], bytes: values[:malloc_increase_bytes] } }
|
29
|
+
perf.merge(mem) { |_k, a, b| a.merge(b) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def profile_report(include_zero: true, sort_index: 0)
|
33
|
+
headers = %w[Method Time Objects Bytes]
|
34
|
+
entries = profile.map { |method, values| [method, values[:time], values[:objects], values[:bytes]] }
|
35
|
+
print_report(entries, headers: headers, include_zero:, sort_index:)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/rakefile.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
|
8
|
+
require 'rubocop/rake_task'
|
9
|
+
|
10
|
+
RuboCop::RakeTask.new
|
11
|
+
|
12
|
+
task default: %i[spec rubocop]
|
13
|
+
|
14
|
+
desc 'Run all available examples'
|
15
|
+
task :examples do
|
16
|
+
examples_dir = File.expand_path('examples', __dir__)
|
17
|
+
scripts = Dir[File.join(examples_dir, '*.rb')].sort
|
18
|
+
if scripts.empty?
|
19
|
+
puts 'No scripts found in examples/'
|
20
|
+
next
|
21
|
+
end
|
22
|
+
|
23
|
+
scripts.each do |script|
|
24
|
+
puts "\n\n=== Running: #{File.basename(script)} ==="
|
25
|
+
system({ 'BUNDLE_GEMFILE' => File.expand_path('Gemfile', __dir__) }, RbConfig.ruby, script)
|
26
|
+
end
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: class-profiler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matthew Greenfield
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-09-04 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: activesupport
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0'
|
19
|
+
type: :development
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: guard
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: guard-rspec
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
type: :development
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: pry
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
type: :development
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: pry-byebug
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
type: :development
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: pry-stack_explorer
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
type: :development
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: rake
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
type: :development
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rspec
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
type: :development
|
118
|
+
prerelease: false
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
- !ruby/object:Gem::Dependency
|
125
|
+
name: rubocop
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
type: :development
|
132
|
+
prerelease: false
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
description: "\n Quickly benchmark execution time and profile memory allocations
|
139
|
+
for specific\n or all instance methods within a class. Include ClassProfiler
|
140
|
+
to get\n `benchmark_methods` and `profile_methods` helpers and collect results
|
141
|
+
via\n `benchmarked` and `profiled_memory`.\n "
|
142
|
+
email:
|
143
|
+
- mattgreenfield1@gmail.com
|
144
|
+
executables: []
|
145
|
+
extensions: []
|
146
|
+
extra_rdoc_files: []
|
147
|
+
files:
|
148
|
+
- ".github/workflows/main.yml"
|
149
|
+
- ".gitignore"
|
150
|
+
- ".rspec"
|
151
|
+
- ".rubocop.yml"
|
152
|
+
- Gemfile
|
153
|
+
- Gemfile.lock
|
154
|
+
- Guardfile
|
155
|
+
- LICENSE.txt
|
156
|
+
- README.md
|
157
|
+
- bin/console
|
158
|
+
- bin/setup
|
159
|
+
- class-profiler.gemspec
|
160
|
+
- config.yml
|
161
|
+
- lib/class_profiler.rb
|
162
|
+
- lib/class_profiler/logging.rb
|
163
|
+
- lib/class_profiler/memory.rb
|
164
|
+
- lib/class_profiler/methods.rb
|
165
|
+
- lib/class_profiler/performance.rb
|
166
|
+
- rakefile.rb
|
167
|
+
homepage: https://github.com/omgreenfield/class-profiler
|
168
|
+
licenses:
|
169
|
+
- MIT
|
170
|
+
metadata: {}
|
171
|
+
rdoc_options: []
|
172
|
+
require_paths:
|
173
|
+
- lib
|
174
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
175
|
+
requirements:
|
176
|
+
- - ">="
|
177
|
+
- !ruby/object:Gem::Version
|
178
|
+
version: 3.1.0
|
179
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
180
|
+
requirements:
|
181
|
+
- - ">="
|
182
|
+
- !ruby/object:Gem::Version
|
183
|
+
version: '0'
|
184
|
+
requirements: []
|
185
|
+
rubygems_version: 3.6.2
|
186
|
+
specification_version: 4
|
187
|
+
summary: Benchmark speed and profile memory of class methods
|
188
|
+
test_files: []
|