runtime_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/.gitignore +12 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +77 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/runtime_profiler +5 -0
- data/bin/setup +8 -0
- data/lib/runtime_profiler.rb +57 -0
- data/lib/runtime_profiler/callbacks/action_controller.rb +36 -0
- data/lib/runtime_profiler/callbacks/active_record.rb +38 -0
- data/lib/runtime_profiler/cli.rb +47 -0
- data/lib/runtime_profiler/events/process_action_event.rb +33 -0
- data/lib/runtime_profiler/events/sql_event.rb +69 -0
- data/lib/runtime_profiler/instrumentation_data.rb +171 -0
- data/lib/runtime_profiler/profiler.rb +55 -0
- data/lib/runtime_profiler/text_report.rb +194 -0
- data/lib/runtime_profiler/version.rb +3 -0
- data/runtime_profiler.gemspec +33 -0
- metadata +218 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f0d303faf46e8b0eec88384965d29c5136390166
|
4
|
+
data.tar.gz: 1653ba57d2cbf4b10dd9c8da35310e2ec6d20f00
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ecdf3e02b864efeec7f8dacc7b96c443c6baaa39a4931af40cb3d720d6519f5094b2f2f10cdb884cc3f3ecb305440b59f4dfe968df9aeb92e3900536fa8814b6
|
7
|
+
data.tar.gz: 26fe71304d4b511468385739f163033f9bc7ec3562168d844068aac47fcb4bd9c540ade25b7cf6dd5ebac05d8184965f740689f4f5d27ebde1eecd1cc1b48a65
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Wilfrido T. Nuqui Jr.
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# runtime_profiler - Runtime Profiler for Rails Applications
|
2
|
+
|
3
|
+
`runtime_profiler` instruments api or a method of your Rails application using Rails' `ActiveSupport::Notifications`
|
4
|
+
|
5
|
+
It then aggregates and generates report to give you insights about specific calls in your Rails application.
|
6
|
+
|
7
|
+
## Note
|
8
|
+
|
9
|
+
This is still a **work in progress**. However, this is a tool meant to be used in development so it is safe to use.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
group :development, :test do
|
17
|
+
... ...
|
18
|
+
gem 'runtime_profiler'
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
And then execute:
|
23
|
+
|
24
|
+
$ bundle
|
25
|
+
|
26
|
+
## Profiling/Instrumenting
|
27
|
+
|
28
|
+
To start profiling, make a test and use `RuntimeProfiler.profile!` method in the tests. The output of instrumentation will be generated under the `tmp` folder of your application.
|
29
|
+
|
30
|
+
Example:
|
31
|
+
```ruby
|
32
|
+
it 'updates user' do
|
33
|
+
RuntimeProfiler.profile!('updates user', [User]) {
|
34
|
+
patch :update, { id: user.id, name: 'Joe' }
|
35
|
+
}
|
36
|
+
|
37
|
+
expect(response.status).to eq(200)
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
Run tests as usual and follow printed instructions after running tests.
|
42
|
+
|
43
|
+
## Reporting
|
44
|
+
|
45
|
+
To see profiling/instrumenting report, please open the report in browser with JSON viewer report. Or you can run the following command:
|
46
|
+
|
47
|
+
```bash
|
48
|
+
bundle exec runtime_profiler view ~/the-rails-app/tmp/runtime-profiling-51079-1521371428.json
|
49
|
+
```
|
50
|
+
|
51
|
+
## Configurations
|
52
|
+
|
53
|
+
All the configurable variables and their defaults are listed below:
|
54
|
+
```ruby
|
55
|
+
RuntimeProfiler.output_path = File.join(Rails.root.to_s, 'tmp')
|
56
|
+
RuntimeProfiler.instrumented_constants = [User]
|
57
|
+
RuntimeProfiler.instrumented_paths = %w(app lib)
|
58
|
+
RuntimeProfiler.instrumented_sql_commands = %w(SELECT INSERT UPDATE DELETE)
|
59
|
+
```
|
60
|
+
|
61
|
+
## Development
|
62
|
+
|
63
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
64
|
+
|
65
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
66
|
+
|
67
|
+
## Contributing
|
68
|
+
|
69
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/wnuqui/runtime_profiler. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
70
|
+
|
71
|
+
## Acknowledgement
|
72
|
+
|
73
|
+
Part of this profiler is based from https://github.com/steventen/sql_tracker.
|
74
|
+
|
75
|
+
## License
|
76
|
+
|
77
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'runtime_profiler'
|
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
|
data/bin/setup
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
require 'runtime_profiler/profiler'
|
4
|
+
require 'method_meter'
|
5
|
+
|
6
|
+
module RuntimeProfiler
|
7
|
+
include ActiveSupport::Configurable
|
8
|
+
|
9
|
+
config_accessor :instrumented_constants do
|
10
|
+
[]
|
11
|
+
end
|
12
|
+
|
13
|
+
config_accessor :instrumented_paths do
|
14
|
+
%w(app lib)
|
15
|
+
end
|
16
|
+
|
17
|
+
config_accessor :instrumented_sql_commands do
|
18
|
+
%w(SELECT INSERT UPDATE DELETE)
|
19
|
+
end
|
20
|
+
|
21
|
+
config_accessor :output_path do
|
22
|
+
if defined?(Rails) && Rails.respond_to?(:root)
|
23
|
+
File.join(Rails.root.to_s, 'tmp')
|
24
|
+
else
|
25
|
+
'tmp'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
def configure
|
31
|
+
Rails.application.eager_load! rescue nil
|
32
|
+
yield self if block_given?
|
33
|
+
end
|
34
|
+
|
35
|
+
def profile!(key, konstants)
|
36
|
+
konstants = konstants.is_a?(Array) ? konstants : [konstants]
|
37
|
+
profiler = Profiler.new(konstants)
|
38
|
+
profiler.prepare_for_instrumentation
|
39
|
+
|
40
|
+
MethodMeter.measure!(key) { yield }
|
41
|
+
|
42
|
+
profiler.save_instrumentation_data
|
43
|
+
end
|
44
|
+
|
45
|
+
def runtime(label='for the block')
|
46
|
+
result = nil
|
47
|
+
|
48
|
+
elapsed_time = Benchmark.realtime { result = yield }
|
49
|
+
|
50
|
+
puts
|
51
|
+
puts '~~~~> ELAPSED TIME (%s): %s' % [label, elapsed_time * 1000]
|
52
|
+
puts
|
53
|
+
|
54
|
+
result
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'runtime_profiler/events/process_action_event'
|
2
|
+
|
3
|
+
module RuntimeProfiler
|
4
|
+
module Callback
|
5
|
+
class ActionController
|
6
|
+
attr_reader :data
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@data = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(*args)
|
13
|
+
event = RuntimeProfiler::ProcessActionEvent.new(args: args)
|
14
|
+
return unless event.recordable?
|
15
|
+
add event
|
16
|
+
end
|
17
|
+
|
18
|
+
def controller_data
|
19
|
+
data.values.first
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def add(event)
|
25
|
+
key = event.key
|
26
|
+
@data[key] = {}
|
27
|
+
|
28
|
+
@data[key][:path] = event.path
|
29
|
+
@data[key][:total_runtime] = event.total_runtime
|
30
|
+
@data[key][:db_runtime] = event.db_runtime
|
31
|
+
@data[key][:view_runtime] = event.view_runtime
|
32
|
+
@data[key][:payload] = event.payload
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'runtime_profiler/events/sql_event'
|
2
|
+
|
3
|
+
module RuntimeProfiler
|
4
|
+
module Callback
|
5
|
+
class ActiveRecord
|
6
|
+
attr_reader :data
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@data = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(*args)
|
13
|
+
event = RuntimeProfiler::SqlEvent.new(args: args, trace: caller)
|
14
|
+
|
15
|
+
return unless event.recordable?
|
16
|
+
return if event.trace.empty?
|
17
|
+
|
18
|
+
@data.key?(event.key) ? update(event) : add(event)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def add(event)
|
24
|
+
key = event.key
|
25
|
+
@data[key] = {}
|
26
|
+
|
27
|
+
@data[key][:sql] = event.sanitized_sql
|
28
|
+
@data[key][:runtimes] = [
|
29
|
+
[ event.total_runtime, event.trace.first ]
|
30
|
+
]
|
31
|
+
end
|
32
|
+
|
33
|
+
def update(event)
|
34
|
+
@data[event.key][:runtimes] << [ event.total_runtime, event.trace.first ]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'commander'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
require_relative 'text_report'
|
5
|
+
|
6
|
+
module RuntimeProfiler
|
7
|
+
class CLI
|
8
|
+
include Commander::Methods
|
9
|
+
|
10
|
+
def run
|
11
|
+
program :name, 'runtime_profiler'
|
12
|
+
program :version, '0.1.0'
|
13
|
+
program :description, 'Display report in console given the JSON report file.'
|
14
|
+
|
15
|
+
command :view do |c|
|
16
|
+
c.syntax = 'runtime_profiler view <profile.report.json> [options]'
|
17
|
+
c.description = 'Display report in console given the JSON report file'
|
18
|
+
|
19
|
+
c.option '--sort-by COLUMN', String, 'Sort by COLUMN. COLUMN can either be "total_calls" or "total_runtime". Default is "total_calls".'
|
20
|
+
c.option '--details TYPE', String, 'TYPE can be "full" or "summary".'
|
21
|
+
c.option '--runtime-above RUNTIME', String, 'RUNTIME is numeric value in ms.'
|
22
|
+
c.option '--only-sqls', String, 'Show only SQL(s).'
|
23
|
+
c.option '--only-methods', String, 'Show only method(s).'
|
24
|
+
c.option '--calls-above CALLS', String, 'CALLS is numeric value.'
|
25
|
+
|
26
|
+
c.action do |args, options|
|
27
|
+
default_options = {
|
28
|
+
sort_by: 'total_calls',
|
29
|
+
details: 'summary',
|
30
|
+
runtime_above: 0,
|
31
|
+
only_sqls: false,
|
32
|
+
only_methods: false,
|
33
|
+
calls_above: 1
|
34
|
+
}
|
35
|
+
|
36
|
+
options.default default_options
|
37
|
+
|
38
|
+
report = RuntimeProfiler::TextReport.new(args.first, options)
|
39
|
+
|
40
|
+
report.print
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
run!
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module RuntimeProfiler
|
2
|
+
class ProcessActionEvent
|
3
|
+
attr_reader :started_at, :finished_at, :payload
|
4
|
+
|
5
|
+
def initialize(args:)
|
6
|
+
_name, @started_at, @finished_at, _unique_id, @payload = args
|
7
|
+
end
|
8
|
+
|
9
|
+
def total_runtime
|
10
|
+
1000.0 * (@finished_at - @started_at)
|
11
|
+
end
|
12
|
+
|
13
|
+
def view_runtime
|
14
|
+
@view_runtime ||= @payload[:view_runtime]
|
15
|
+
end
|
16
|
+
|
17
|
+
def db_runtime
|
18
|
+
@db_runtime ||= @payload[:db_runtime]
|
19
|
+
end
|
20
|
+
|
21
|
+
def recordable?
|
22
|
+
true # NB: We may be putting login on this in the future
|
23
|
+
end
|
24
|
+
|
25
|
+
def path
|
26
|
+
@path ||= @payload[:path].gsub(/(\S=)(?:(?!&).)+/, '\1xxx').gsub(/(\d+)/, 'xxx')
|
27
|
+
end
|
28
|
+
|
29
|
+
def key
|
30
|
+
@key ||= Digest::MD5.hexdigest(path.downcase)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module RuntimeProfiler
|
2
|
+
class SqlEvent
|
3
|
+
attr_reader :started_at, :finished_at, :payload, :trace
|
4
|
+
|
5
|
+
def initialize(args: , trace: )
|
6
|
+
_name, @started_at, @finished_at, _unique_id, @payload = args
|
7
|
+
@trace = sanitize_trace!(trace)
|
8
|
+
end
|
9
|
+
|
10
|
+
def recordable?
|
11
|
+
return true unless RuntimeProfiler.instrumented_sql_commands.respond_to?(:join)
|
12
|
+
instrumented_sql_matcher =~ sql
|
13
|
+
end
|
14
|
+
|
15
|
+
def total_runtime
|
16
|
+
1000.0 * (@finished_at - @started_at)
|
17
|
+
end
|
18
|
+
|
19
|
+
def sanitized_sql
|
20
|
+
sql.squish!
|
21
|
+
|
22
|
+
sql.gsub!(/(\s(=|>|<|>=|<=|<>|!=)\s)('[^']+'|[\$\+\-\w\.]+)/, '\1xxx')
|
23
|
+
sql.gsub!(/(\sIN\s)\([^\(\)]+\)/i, '\1(xxx)')
|
24
|
+
sql.gsub!(/(\sBETWEEN\s)('[^']+'|[\+\-\w\.]+)(\sAND\s)('[^']+'|[\+\-\w\.]+)/i, '\1xxx\3xxx')
|
25
|
+
sql.gsub!(/(\sVALUES\s)\(.+\)/i, '\1(xxx)')
|
26
|
+
sql.gsub!(/(\s(LIKE|ILIKE|SIMILAR TO|NOT SIMILAR TO)\s)('[^']+')/i, '\1xxx')
|
27
|
+
sql.gsub!(/(\s(LIMIT|OFFSET)\s)(\d+)/i, '\1xxx')
|
28
|
+
|
29
|
+
sql
|
30
|
+
end
|
31
|
+
|
32
|
+
def key
|
33
|
+
@key ||= Digest::MD5.hexdigest(sql.downcase)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def sql
|
39
|
+
@sql ||= @payload[:sql].dup
|
40
|
+
end
|
41
|
+
|
42
|
+
def instrumented_sql_matcher
|
43
|
+
@instrumented_sql_matcher ||= /\A#{RuntimeProfiler.instrumented_sql_commands.join('|')}/i
|
44
|
+
end
|
45
|
+
|
46
|
+
def trace_path_matcher
|
47
|
+
@trace_path_matcher ||= %r{^(#{RuntimeProfiler.instrumented_paths.join('|')})\/}
|
48
|
+
end
|
49
|
+
|
50
|
+
def sanitize_trace!(trace)
|
51
|
+
return trace unless defined?(Rails)
|
52
|
+
return trace unless Rails.respond_to?(:backtrace_cleaner)
|
53
|
+
|
54
|
+
if Rails.backtrace_cleaner.instance_variable_get(:@root) == '/'
|
55
|
+
Rails.backtrace_cleaner.instance_variable_set :@root, Rails.root.to_s
|
56
|
+
end
|
57
|
+
|
58
|
+
Rails.backtrace_cleaner.remove_silencers!
|
59
|
+
|
60
|
+
if RuntimeProfiler.instrumented_paths.respond_to?(:join)
|
61
|
+
Rails.backtrace_cleaner.add_silencer do |line|
|
62
|
+
line !~ trace_path_matcher
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
Rails.backtrace_cleaner.clean(trace)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
module RuntimeProfiler
|
2
|
+
class InstrumentationData
|
3
|
+
attr_reader :controller_data, :sql_data
|
4
|
+
|
5
|
+
def initialize(controller_data: nil, sql_data: nil)
|
6
|
+
@controller_data = controller_data
|
7
|
+
@sql_data = sql_data
|
8
|
+
end
|
9
|
+
|
10
|
+
def persist!
|
11
|
+
instrumented_api = [controller_data[:payload][:controller], controller_data[:payload][:action]].join('#')
|
12
|
+
|
13
|
+
instrumentation_data = {
|
14
|
+
instrumentation: {
|
15
|
+
instrumented_api: instrumented_api,
|
16
|
+
summary: {
|
17
|
+
db_runtime: controller_data[:db_runtime],
|
18
|
+
view_runtime: controller_data[:view_runtime],
|
19
|
+
total_runtime: controller_data[:total_runtime],
|
20
|
+
slowest_sql: sql_calls_data[:slowest_sql],
|
21
|
+
mostly_called_sql: sql_calls_data[:mostly_called_sql],
|
22
|
+
total_sql_calls: sql_calls_data[:total_sql_calls],
|
23
|
+
total_unique_sql_calls: sql_calls_data[:total_unique_sql_calls],
|
24
|
+
slowest_method: method_calls_data[:slowest_method],
|
25
|
+
mostly_called_method: method_calls_data[:mostly_called_method]
|
26
|
+
},
|
27
|
+
instrumented_sql_calls: sql_calls_data[:instrumented_sql_calls],
|
28
|
+
instrumented_methods: method_calls_data[:instrumented_methods],
|
29
|
+
instrumented_at: Time.now
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
FileUtils.mkdir_p(RuntimeProfiler.output_path)
|
34
|
+
filename = ['runtime-profiling', Process.pid, Time.now.to_i].join('-') << '.json'
|
35
|
+
output_file = File.join(RuntimeProfiler.output_path, filename)
|
36
|
+
|
37
|
+
File.open(output_file, 'w') do |f|
|
38
|
+
f.write JSON.dump(instrumentation_data)
|
39
|
+
end
|
40
|
+
|
41
|
+
puts '~~~~> [ Profiling RUNTIME ] Profiling now COMPLETE and JSON report written at ' + output_file.to_s
|
42
|
+
puts '~~~~> [ Profiling RUNTIME ]'
|
43
|
+
puts '~~~~> [ Profiling RUNTIME ] You can do the following to view the JSON report in console:'
|
44
|
+
puts '~~~~> [ Profiling RUNTIME ]'
|
45
|
+
puts '~~~~> [ Profiling RUNTIME ] bundle exec runtime_profiler view ' + output_file.to_s
|
46
|
+
puts '~~~~> [ Profiling RUNTIME ]'
|
47
|
+
puts '~~~~> [ Profiling RUNTIME ] Or'
|
48
|
+
puts '~~~~> [ Profiling RUNTIME ]'
|
49
|
+
puts '~~~~> [ Profiling RUNTIME ] bundle exec runtime_profiler view --help'
|
50
|
+
puts '~~~~> [ Profiling RUNTIME ]'
|
51
|
+
puts '~~~~> [ Profiling RUNTIME ] for more details.'
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def method_calls_data
|
57
|
+
@method_calls_data ||= begin
|
58
|
+
instrumented_methods = {}
|
59
|
+
|
60
|
+
# TODO: Group methods under a key and under an object
|
61
|
+
MethodMeter.measurement.each do |measurement|
|
62
|
+
measurement.each_pair do |key, data|
|
63
|
+
data.each do |d|
|
64
|
+
object = d[:method].split(separator = '.')
|
65
|
+
object = d[:method].split(separator = '#') if object.length == 1
|
66
|
+
|
67
|
+
d[:method] = separator + object.second
|
68
|
+
|
69
|
+
if instrumented_methods[object.first]
|
70
|
+
instrumented_methods[object.first] << d
|
71
|
+
else
|
72
|
+
instrumented_methods[object.first] = [d]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
instrumented_methods = instrumented_methods.inject({}) do |hash, (key, value)|
|
79
|
+
val = value.sort { |a, b| b[:total_runtime] <=> a[:total_runtime] }
|
80
|
+
hash[key] = val
|
81
|
+
hash
|
82
|
+
end
|
83
|
+
|
84
|
+
slowest_method = {total_runtime: 0}
|
85
|
+
mostly_called_method = {total_calls: 0}
|
86
|
+
|
87
|
+
instrumented_methods.each do |profiled_object_name, methods|
|
88
|
+
# sort using `total_runtime` in DESC order
|
89
|
+
_methods = methods.sort { |a, b| b[:total_runtime] <=> a[:total_runtime] }
|
90
|
+
slowest = _methods[0]
|
91
|
+
|
92
|
+
if slowest[:total_runtime] > slowest_method[:total_runtime]
|
93
|
+
slowest_method[:method] = [profiled_object_name, slowest[:method]].join
|
94
|
+
slowest_method[:total_runtime] = slowest[:total_runtime]
|
95
|
+
end
|
96
|
+
|
97
|
+
# sort using `total_calls` in DESC order and GET first item
|
98
|
+
mostly_called = methods.sort { |a, b| b[:total_calls] <=> a[:total_calls] } [0]
|
99
|
+
|
100
|
+
if mostly_called[:total_calls] > mostly_called_method[:total_calls]
|
101
|
+
mostly_called_method[:method] = [profiled_object_name, mostly_called[:method]].join
|
102
|
+
mostly_called_method[:total_runtime] = mostly_called[:total_runtime]
|
103
|
+
mostly_called_method[:total_calls] = mostly_called[:total_calls]
|
104
|
+
mostly_called_method[:min] = mostly_called[:min]
|
105
|
+
mostly_called_method[:max] = mostly_called[:max]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
{
|
110
|
+
instrumented_methods: instrumented_methods,
|
111
|
+
slowest_method: slowest_method,
|
112
|
+
mostly_called_method: mostly_called_method
|
113
|
+
}
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def sql_calls_data
|
118
|
+
@sql_calls_data ||= begin
|
119
|
+
instrumented_sql_calls = []
|
120
|
+
|
121
|
+
slowest_sql = {total_runtime: 0}
|
122
|
+
mostly_called_sql = {total_calls: 0}
|
123
|
+
|
124
|
+
sql_data.values.each do |value|
|
125
|
+
total_calls = value[:runtimes].size
|
126
|
+
total_runtime = value[:runtimes].map { |runtime| runtime[0] }.reduce(:+)
|
127
|
+
average = total_runtime / total_calls
|
128
|
+
|
129
|
+
# sort using `runtimes` in DESC order
|
130
|
+
runtimes = value[:runtimes].sort { |a, b| b[0] <=> a[0] }
|
131
|
+
slowest = runtimes[0]
|
132
|
+
fastest = runtimes[runtimes.size - 1]
|
133
|
+
|
134
|
+
if slowest[0] > slowest_sql[:total_runtime]
|
135
|
+
slowest_sql[:sql] = value[:sql]
|
136
|
+
slowest_sql[:total_runtime] = slowest[0]
|
137
|
+
slowest_sql[:source] = slowest[1]
|
138
|
+
end
|
139
|
+
|
140
|
+
instrumented_sql_calls << {
|
141
|
+
sql: value[:sql],
|
142
|
+
runtimes: runtimes,
|
143
|
+
total_calls: total_calls,
|
144
|
+
total_runtime: total_runtime,
|
145
|
+
average: average,
|
146
|
+
min: fastest[0],
|
147
|
+
max: slowest[0]
|
148
|
+
}
|
149
|
+
|
150
|
+
if total_calls > mostly_called_sql[:total_calls]
|
151
|
+
mostly_called_sql[:sql] = value[:sql]
|
152
|
+
mostly_called_sql[:runtimes] = value[:runtimes]
|
153
|
+
mostly_called_sql[:total_calls] = total_calls
|
154
|
+
mostly_called_sql[:total_runtime] = total_runtime
|
155
|
+
mostly_called_sql[:average] = average
|
156
|
+
mostly_called_sql[:min] = fastest[0]
|
157
|
+
mostly_called_sql[:max] = slowest[0]
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
{
|
162
|
+
instrumented_sql_calls: instrumented_sql_calls.sort { |a, b| b[:max] <=> a[:max] },
|
163
|
+
total_sql_calls: instrumented_sql_calls.map { |sql_call| sql_call[:total_calls] }.reduce(:+),
|
164
|
+
total_unique_sql_calls: instrumented_sql_calls.size,
|
165
|
+
slowest_sql: slowest_sql,
|
166
|
+
mostly_called_sql: mostly_called_sql
|
167
|
+
}
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# require 'method_profiler'
|
2
|
+
|
3
|
+
require 'runtime_profiler/callbacks/active_record'
|
4
|
+
require 'runtime_profiler/callbacks/action_controller'
|
5
|
+
require 'runtime_profiler/instrumentation_data'
|
6
|
+
|
7
|
+
module RuntimeProfiler
|
8
|
+
class Profiler
|
9
|
+
attr_accessor :instrumented_constants
|
10
|
+
|
11
|
+
def initialize(konstants)
|
12
|
+
self.instrumented_constants = konstants
|
13
|
+
end
|
14
|
+
|
15
|
+
def prepare_for_instrumentation
|
16
|
+
subscribe_to_event_notifications
|
17
|
+
prepare_methods_to_instrument
|
18
|
+
end
|
19
|
+
|
20
|
+
def subscribe_to_event_notifications
|
21
|
+
@subscribers = []
|
22
|
+
|
23
|
+
@active_record_callback = Callback::ActiveRecord.new
|
24
|
+
|
25
|
+
@subscribers << ActiveSupport::Notifications
|
26
|
+
.subscribe('sql.active_record', @active_record_callback)
|
27
|
+
|
28
|
+
@action_controller_callback = Callback::ActionController.new
|
29
|
+
|
30
|
+
@subscribers << ActiveSupport::Notifications
|
31
|
+
.subscribe('process_action.action_controller', @action_controller_callback)
|
32
|
+
end
|
33
|
+
|
34
|
+
def unsubscribe_to_event_notifications
|
35
|
+
@subscribers.each do |subscriber|
|
36
|
+
ActiveSupport::Notifications.unsubscribe(subscriber)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def prepare_methods_to_instrument
|
41
|
+
self.instrumented_constants.flatten
|
42
|
+
.each { |constant| MethodMeter.observe(constant) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def save_instrumentation_data
|
46
|
+
unsubscribe_to_event_notifications
|
47
|
+
|
48
|
+
instrumentation_data = RuntimeProfiler::InstrumentationData.new \
|
49
|
+
controller_data: @action_controller_callback.controller_data,
|
50
|
+
sql_data: @active_record_callback.data
|
51
|
+
|
52
|
+
instrumentation_data.persist!
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'hirb'
|
2
|
+
require 'terminal-table'
|
3
|
+
require 'active_support/core_ext/string'
|
4
|
+
|
5
|
+
module RuntimeProfiler
|
6
|
+
class TextReport
|
7
|
+
COUNT_WIDTH = 5
|
8
|
+
DURATION_WIDTH = 22
|
9
|
+
TOTAL_RUNTIME_WIDTH = 20
|
10
|
+
|
11
|
+
SUMMARY_TEMPLATE = <<-EOT.strip_heredoc
|
12
|
+
|
13
|
+
\e[1mPROFILING REPORT\e[22m
|
14
|
+
----------------
|
15
|
+
|
16
|
+
\e[1mRUNTIME\e[22m
|
17
|
+
Total Runtime : %s ms
|
18
|
+
Database Runtime : %s ms
|
19
|
+
View Runtime : %s ms
|
20
|
+
|
21
|
+
\e[1mMETHODS\e[22m
|
22
|
+
SLOWEST : %s (%s ms)
|
23
|
+
MOSTLY CALLED : %s (%s number of calls in %s ms)
|
24
|
+
|
25
|
+
\e[1mSQL CALLS\e[22m
|
26
|
+
Total : %s
|
27
|
+
Total Unique : %s
|
28
|
+
|
29
|
+
\e[1mSLOWEST\e[22m
|
30
|
+
Total Runtime : %s ms
|
31
|
+
SQL : %s
|
32
|
+
Source : %s
|
33
|
+
|
34
|
+
\e[1mMOSTLY CALLED\e[22m
|
35
|
+
Total Calls : %s
|
36
|
+
Total Runtime : %s ms
|
37
|
+
SQL : %s
|
38
|
+
Sources : %s
|
39
|
+
|
40
|
+
EOT
|
41
|
+
|
42
|
+
attr_accessor :data, :options
|
43
|
+
|
44
|
+
def initialize(json_file, options)
|
45
|
+
self.data = JSON.parse( File.read(json_file) )
|
46
|
+
self.options = options
|
47
|
+
end
|
48
|
+
|
49
|
+
def print
|
50
|
+
print_summary
|
51
|
+
|
52
|
+
if self.options.details == 'full'
|
53
|
+
print_instrumented_methods
|
54
|
+
print_instrumented_sql_calls
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def print_summary
|
61
|
+
summary = SUMMARY_TEMPLATE % summary_template_data
|
62
|
+
puts summary
|
63
|
+
end
|
64
|
+
|
65
|
+
def print_instrumented_methods
|
66
|
+
instrumented_methods = []
|
67
|
+
|
68
|
+
self.data['instrumentation']['instrumented_methods'].each do |profiled_object_name, methods|
|
69
|
+
instrumented_methods.concat methods.map { |method| method['method'] = [profiled_object_name, method['method']].join; method}
|
70
|
+
end
|
71
|
+
|
72
|
+
instrumented_methods = sort(instrumented_methods)
|
73
|
+
|
74
|
+
table = Terminal::Table.new do |t|
|
75
|
+
t.headings = ['Method', 'Total Runtime (ms)', 'Total Calls', 'Min', 'Max']
|
76
|
+
|
77
|
+
instrumented_methods.each_with_index do |row, index|
|
78
|
+
t.add_row [
|
79
|
+
row['method'],
|
80
|
+
row['total_runtime'],
|
81
|
+
row['total_calls'],
|
82
|
+
row['min'],
|
83
|
+
row['max']
|
84
|
+
]
|
85
|
+
t.add_separator if index < instrumented_methods.size - 1
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
puts
|
91
|
+
puts
|
92
|
+
puts "\e[1mINSTRUMENTED METHOD(s)\e[22m"
|
93
|
+
puts
|
94
|
+
puts table
|
95
|
+
end
|
96
|
+
|
97
|
+
def print_instrumented_sql_calls
|
98
|
+
instrumented_sql_calls = sort(self.data['instrumentation']['instrumented_sql_calls'])
|
99
|
+
|
100
|
+
table = Terminal::Table.new do |t|
|
101
|
+
t.headings = ['Count', 'Total Runtime (ms)', 'Average Runtime (ms)', 'SQL Query', 'Source']
|
102
|
+
|
103
|
+
instrumented_sql_calls.each_with_index do |row, index|
|
104
|
+
chopped_sql = wrap_text(row['sql'], sql_width)
|
105
|
+
source_list = wrap_list(row['runtimes'].map { |runtime| runtime[1] }.uniq, sql_width - 15)
|
106
|
+
average_runtime = row['average'].round(2)
|
107
|
+
total_lines = if chopped_sql.length >= source_list.length
|
108
|
+
chopped_sql.length
|
109
|
+
else
|
110
|
+
source_list.length
|
111
|
+
end
|
112
|
+
|
113
|
+
(0...total_lines).each do |line|
|
114
|
+
count = line == 0 ? row['total_calls'] : ''
|
115
|
+
average = line == 0 ? average_runtime : ''
|
116
|
+
total_runtime = line == 0 ? row['total_runtime'] : ''
|
117
|
+
source = source_list.length > line ? source_list[line] : ''
|
118
|
+
query = row['sql'].length > line ? chopped_sql[line] : ''
|
119
|
+
|
120
|
+
t.add_row []
|
121
|
+
t.add_row [count, total_runtime, average, query, source]
|
122
|
+
end
|
123
|
+
|
124
|
+
t.add_row []
|
125
|
+
t.add_separator if index < instrumented_sql_calls.size - 1
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
puts
|
131
|
+
puts
|
132
|
+
puts "\e[1mINSTRUMENTED SQL(s)\e[22m"
|
133
|
+
puts
|
134
|
+
puts table
|
135
|
+
end
|
136
|
+
|
137
|
+
def wrap_text(text, width)
|
138
|
+
return [text] if text.length <= width
|
139
|
+
text.scan(/.{1,#{width}}/)
|
140
|
+
end
|
141
|
+
|
142
|
+
def wrap_list(list, width)
|
143
|
+
list.map do |text|
|
144
|
+
wrap_text(text, width)
|
145
|
+
end.flatten
|
146
|
+
end
|
147
|
+
|
148
|
+
def sql_width
|
149
|
+
@sql_width ||= begin
|
150
|
+
terminal_width = Hirb::Util.detect_terminal_size.first
|
151
|
+
(terminal_width - COUNT_WIDTH - DURATION_WIDTH - TOTAL_RUNTIME_WIDTH) / 2
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def sort(data)
|
156
|
+
data.sort_by do |d|
|
157
|
+
if self.options.sort_by == 'total_runtime'
|
158
|
+
-d['total_runtime']
|
159
|
+
else
|
160
|
+
-d['total_calls']
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def summary_template_data
|
166
|
+
summary = self.data['instrumentation']['summary']
|
167
|
+
|
168
|
+
[
|
169
|
+
summary['total_runtime'].round(2),
|
170
|
+
summary['db_runtime'].round(2),
|
171
|
+
summary['view_runtime'].round(2),
|
172
|
+
|
173
|
+
summary['slowest_method']['method'],
|
174
|
+
summary['slowest_method']['total_runtime'].round(2),
|
175
|
+
|
176
|
+
summary['mostly_called_method']['method'],
|
177
|
+
summary['mostly_called_method']['total_calls'],
|
178
|
+
summary['mostly_called_method']['total_runtime'].round(2),
|
179
|
+
|
180
|
+
summary['total_sql_calls'],
|
181
|
+
summary['total_unique_sql_calls'],
|
182
|
+
|
183
|
+
summary['slowest_sql']['total_runtime'].round(2),
|
184
|
+
summary['slowest_sql']['sql'],
|
185
|
+
summary['slowest_sql']['source'],
|
186
|
+
|
187
|
+
summary['mostly_called_sql']['total_calls'],
|
188
|
+
summary['mostly_called_sql']['total_runtime'].round(2),
|
189
|
+
summary['mostly_called_sql']['sql'],
|
190
|
+
summary['mostly_called_sql']['runtimes'].map { |runtime| runtime[1] }.uniq
|
191
|
+
]
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'runtime_profiler/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'runtime_profiler'
|
8
|
+
spec.version = RuntimeProfiler::VERSION
|
9
|
+
spec.authors = ['Wilfrido T. Nuqui Jr.']
|
10
|
+
spec.email = ['nuqui.dev@gmail.com']
|
11
|
+
|
12
|
+
spec.summary = 'Runtime Profiler for Rails Applications'
|
13
|
+
spec.description = 'Runtime Profiler for Rails Applications'
|
14
|
+
spec.homepage = 'http://www.github.com/wnuqui/runtime_profiler'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = 'bin'
|
19
|
+
spec.executables = ['runtime_profiler']
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_development_dependency 'bundler', '~> 1.12'
|
23
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
24
|
+
spec.add_development_dependency 'minitest', '~> 5.0'
|
25
|
+
spec.add_development_dependency 'activesupport', '>= 3.0.0'
|
26
|
+
spec.add_development_dependency 'pry'
|
27
|
+
spec.add_development_dependency 'minitest-line'
|
28
|
+
spec.add_runtime_dependency 'terminal-table'
|
29
|
+
spec.add_runtime_dependency 'commander'
|
30
|
+
spec.add_runtime_dependency 'hirb'
|
31
|
+
spec.add_runtime_dependency 'method_meter'
|
32
|
+
spec.add_runtime_dependency 'defined_methods'
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: runtime_profiler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Wilfrido T. Nuqui Jr.
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-03-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.12'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.12'
|
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: activesupport
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.0.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.0.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: minitest-line
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: terminal-table
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: commander
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: hirb
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: method_meter
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: defined_methods
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :runtime
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
description: Runtime Profiler for Rails Applications
|
168
|
+
email:
|
169
|
+
- nuqui.dev@gmail.com
|
170
|
+
executables:
|
171
|
+
- runtime_profiler
|
172
|
+
extensions: []
|
173
|
+
extra_rdoc_files: []
|
174
|
+
files:
|
175
|
+
- ".gitignore"
|
176
|
+
- Gemfile
|
177
|
+
- LICENSE.txt
|
178
|
+
- README.md
|
179
|
+
- Rakefile
|
180
|
+
- bin/console
|
181
|
+
- bin/runtime_profiler
|
182
|
+
- bin/setup
|
183
|
+
- lib/runtime_profiler.rb
|
184
|
+
- lib/runtime_profiler/callbacks/action_controller.rb
|
185
|
+
- lib/runtime_profiler/callbacks/active_record.rb
|
186
|
+
- lib/runtime_profiler/cli.rb
|
187
|
+
- lib/runtime_profiler/events/process_action_event.rb
|
188
|
+
- lib/runtime_profiler/events/sql_event.rb
|
189
|
+
- lib/runtime_profiler/instrumentation_data.rb
|
190
|
+
- lib/runtime_profiler/profiler.rb
|
191
|
+
- lib/runtime_profiler/text_report.rb
|
192
|
+
- lib/runtime_profiler/version.rb
|
193
|
+
- runtime_profiler.gemspec
|
194
|
+
homepage: http://www.github.com/wnuqui/runtime_profiler
|
195
|
+
licenses:
|
196
|
+
- MIT
|
197
|
+
metadata: {}
|
198
|
+
post_install_message:
|
199
|
+
rdoc_options: []
|
200
|
+
require_paths:
|
201
|
+
- lib
|
202
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
203
|
+
requirements:
|
204
|
+
- - ">="
|
205
|
+
- !ruby/object:Gem::Version
|
206
|
+
version: '0'
|
207
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
208
|
+
requirements:
|
209
|
+
- - ">="
|
210
|
+
- !ruby/object:Gem::Version
|
211
|
+
version: '0'
|
212
|
+
requirements: []
|
213
|
+
rubyforge_project:
|
214
|
+
rubygems_version: 2.6.13
|
215
|
+
signing_key:
|
216
|
+
specification_version: 4
|
217
|
+
summary: Runtime Profiler for Rails Applications
|
218
|
+
test_files: []
|