runtime_profiler 0.3.0 → 0.4.4
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 +4 -4
- data/README.md +68 -19
- data/lib/runtime_profiler.rb +20 -17
- data/lib/runtime_profiler/callbacks/action_controller.rb +1 -0
- data/lib/runtime_profiler/callbacks/active_record.rb +2 -2
- data/lib/runtime_profiler/cli.rb +1 -1
- data/lib/runtime_profiler/{instrumentation_data.rb → data.rb} +51 -54
- data/lib/runtime_profiler/events/process_action_event.rb +1 -1
- data/lib/runtime_profiler/events/sql_event.rb +6 -6
- data/lib/runtime_profiler/profiler.rb +31 -22
- data/lib/runtime_profiler/text_report.rb +40 -41
- data/lib/runtime_profiler/version.rb +1 -1
- data/rp_view_command.png +0 -0
- data/runtime_profiler.gemspec +8 -8
- metadata +33 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c65d8e5bf6fc5d9c3a429de889693927953c5194ae5a3562c76f2eed92f5ab09
|
4
|
+
data.tar.gz: 3067f05a450557a11c1a8c44d0573fae93fdef06b7daecde1b7c1ed1a6bfd6be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6712090d8b730c59d6250a12391962233b759dfeca38713b7cfe5bb6d714d6e99e38bef7b47262c30720b68f543b34e227d6e8cee73d1076fe68aba476755d3b
|
7
|
+
data.tar.gz: 454623783d6444cac238637b86f45e741479eeb1295f5ac246a003ac91a5b8f7b7be8a614fff1123f7c7c3e54bf077df8f5787173bcda1f7aac984fd74627ce8
|
data/README.md
CHANGED
@@ -1,16 +1,36 @@
|
|
1
|
-
# runtime_profiler
|
1
|
+
# runtime_profiler
|
2
2
|
|
3
|
-
|
3
|
+
*A runtime profiler for Rails applications.*
|
4
4
|
|
5
|
-
|
5
|
+
Check which part of your Rails application is causing slow response time. **runtime_profiler** gives you an easy way to find performance problems by profiling an endpoint or a method in your Rails application.
|
6
6
|
|
7
|
-
|
7
|
+
[](https://wnuqui.semaphoreci.com/projects/runtime_profiler)
|
8
|
+
|
9
|
+
## Table of contents
|
10
|
+
|
11
|
+
- [Getting Started](#getting-started)
|
12
|
+
- [Installing](#installing)
|
13
|
+
- [Profiling](#profiling)
|
14
|
+
- [Structure](#structure)
|
15
|
+
- [Examples](#examples)
|
16
|
+
- [Viewing Profiling Result](#viewing-profiling-result)
|
17
|
+
- [view Options](#view-options)
|
18
|
+
- [Screenshot](#screenshot)
|
19
|
+
- [Configurations](#configurations)
|
20
|
+
- [Development](#development)
|
21
|
+
- [Contributing](#contributing)
|
22
|
+
- [Acknowledgement](#acknowledgement)
|
23
|
+
- [License](#license)
|
24
|
+
|
25
|
+
## Getting Started
|
26
|
+
|
27
|
+
### Installing
|
8
28
|
|
9
29
|
Add this line to your application's Gemfile:
|
10
30
|
|
11
31
|
```ruby
|
32
|
+
# In your Gemfile
|
12
33
|
group :development, :test do
|
13
|
-
... ...
|
14
34
|
gem 'runtime_profiler'
|
15
35
|
end
|
16
36
|
```
|
@@ -19,13 +39,33 @@ And then execute:
|
|
19
39
|
|
20
40
|
$ bundle
|
21
41
|
|
22
|
-
|
42
|
+
Or install it yourself as:
|
43
|
+
|
44
|
+
$ gem install runtime_profiler
|
45
|
+
|
46
|
+
### Profiling
|
47
|
+
|
48
|
+
#### Structure
|
49
|
+
|
50
|
+
To profile a specific class (model, controller, etc), all you need to do is to wrap a line where the target class (or instance) is calling a method (entry point of profiling).
|
23
51
|
|
24
|
-
|
52
|
+
```ruby
|
53
|
+
# Profiles runtime of `ClassToProfile` class.
|
54
|
+
RuntimeProfiler.profile!('description', [ClassToProfile]) {
|
55
|
+
# one line where `ClassToProfile` (or its instance) is calling a method
|
56
|
+
}
|
57
|
+
```
|
58
|
+
|
59
|
+
Since the second argument of `.profile!` accepts array of classes, then you can provide all target classes that you want to profile.
|
60
|
+
|
61
|
+
#### Examples
|
62
|
+
|
63
|
+
You can make a test that targets a particular endpoint (or even just a method) and use `RuntimeProfiler.profile!` method in the test.
|
25
64
|
|
26
|
-
Example of a test code wrap by `RuntimeProfiler.profile!` method:
|
27
65
|
```ruby
|
28
66
|
it 'updates user' do
|
67
|
+
# Profiles runtime of PUT /users/:id endpoint and
|
68
|
+
# specifically interested with the methods of `User` model.
|
29
69
|
RuntimeProfiler.profile!('updates user', [User]) {
|
30
70
|
patch :update, { id: user.id, name: 'Joe' }
|
31
71
|
}
|
@@ -36,25 +76,26 @@ end
|
|
36
76
|
|
37
77
|
Run the test as usual and follow printed instructions after running.
|
38
78
|
|
39
|
-
|
79
|
+
Or if you prefer writing just code snippet, then just wrap the snippet with `RuntimeProfiler.profile!` method:
|
40
80
|
```ruby
|
81
|
+
# Profiles runtime of `UserMailer` mailer.
|
41
82
|
RuntimeProfiler.profile!('UserMailer', [UserMailer]) {
|
42
83
|
user = User.last
|
43
84
|
UserMailer.with(user: user).weekly_summary.deliver_now
|
44
85
|
}
|
45
86
|
```
|
46
87
|
|
47
|
-
**Note:** The code (
|
88
|
+
**Note:** The code (test or not) where `RuntimeProfiler.profile!` is used must be **free from any mocking/stubbing** since the goal is to check performance bottlenecks.
|
48
89
|
|
49
|
-
|
90
|
+
### Viewing Profiling Result
|
50
91
|
|
51
|
-
To see profiling
|
92
|
+
To see profiling report, you can open the report in browser with JSON viewer report. Or you can run the following command:
|
52
93
|
|
53
94
|
```bash
|
54
|
-
bundle exec runtime_profiler view
|
95
|
+
bundle exec runtime_profiler view tmp/rp-124094-1608308786.json
|
55
96
|
```
|
56
97
|
|
57
|
-
|
98
|
+
#### view options
|
58
99
|
|
59
100
|
Here are the command line options for `runtime_profiler view` command.
|
60
101
|
|
@@ -97,14 +138,22 @@ $ bundle exec runtime_profiler view --help
|
|
97
138
|
ROUNDING is integer value. Used in rounding runtimes. Default is 4.
|
98
139
|
```
|
99
140
|
|
100
|
-
|
141
|
+
### Screenshot
|
142
|
+
|
143
|
+
<p align="center">
|
144
|
+
<img src="rp_view_command.png">
|
145
|
+
</p>
|
146
|
+
|
147
|
+
### Configurations
|
101
148
|
|
102
|
-
All the configurable variables and their defaults are listed below.
|
149
|
+
All the configurable variables and their defaults are listed below. There is no one correct place where to put these configurations. It can be inside `config/initializers` folder of your Rails application. Or if you are using test to profile, it can be in the last part of `spec/spec_helper.rb`.
|
103
150
|
```ruby
|
104
151
|
RuntimeProfiler.output_path = File.join(Rails.root.to_s, 'tmp')
|
105
|
-
RuntimeProfiler.
|
106
|
-
RuntimeProfiler.
|
107
|
-
RuntimeProfiler.
|
152
|
+
RuntimeProfiler.profiled_constants = [User]
|
153
|
+
RuntimeProfiler.profiled_paths = %w(app lib)
|
154
|
+
RuntimeProfiler.profiled_sql_commands = %w(SELECT INSERT UPDATE DELETE)
|
155
|
+
# Useful when you want to exclude in profiling specific method(s) from framework/library being used
|
156
|
+
RuntimeProfiler.excepted_methods = [:attribute_type_decorations, :_validators, :defined_enums]
|
108
157
|
```
|
109
158
|
|
110
159
|
## Development
|
data/lib/runtime_profiler.rb
CHANGED
@@ -1,45 +1,48 @@
|
|
1
1
|
require 'active_support'
|
2
2
|
|
3
3
|
require 'runtime_profiler/profiler'
|
4
|
-
require 'method_meter'
|
5
4
|
|
6
5
|
module RuntimeProfiler
|
7
6
|
include ActiveSupport::Configurable
|
8
7
|
|
9
|
-
config_accessor :
|
8
|
+
config_accessor :profiled_constants do
|
10
9
|
[]
|
11
10
|
end
|
12
11
|
|
13
|
-
config_accessor :
|
14
|
-
%w
|
12
|
+
config_accessor :profiled_paths do
|
13
|
+
%w[app lib]
|
15
14
|
end
|
16
15
|
|
17
|
-
config_accessor :
|
18
|
-
%w
|
16
|
+
config_accessor :profiled_sql_commands do
|
17
|
+
%w[SELECT INSERT UPDATE DELETE]
|
19
18
|
end
|
20
19
|
|
21
20
|
config_accessor :output_path do
|
22
|
-
if defined?(Rails) && Rails.respond_to?(:root)
|
21
|
+
if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
|
23
22
|
File.join(Rails.root.to_s, 'tmp')
|
24
23
|
else
|
25
24
|
'tmp'
|
26
25
|
end
|
27
26
|
end
|
28
27
|
|
28
|
+
config_accessor :excepted_methods do
|
29
|
+
[]
|
30
|
+
end
|
31
|
+
|
29
32
|
class << self
|
30
33
|
def configure
|
31
|
-
|
34
|
+
begin
|
35
|
+
Rails.application.eager_load!
|
36
|
+
rescue StandardError
|
37
|
+
nil
|
38
|
+
end
|
32
39
|
yield self if block_given?
|
33
40
|
end
|
34
41
|
|
35
|
-
def profile!(key,
|
36
|
-
|
37
|
-
profiler = Profiler.new(
|
38
|
-
profiler.
|
39
|
-
|
40
|
-
MethodMeter.measure!(key) { yield }
|
41
|
-
|
42
|
-
profiler.save_instrumentation_data
|
42
|
+
def profile!(key, constants)
|
43
|
+
constants = constants.is_a?(Array) ? constants : [constants]
|
44
|
+
profiler = Profiler.new(constants)
|
45
|
+
profiler.profile!(key) { yield }
|
43
46
|
end
|
44
47
|
end
|
45
|
-
end
|
48
|
+
end
|
@@ -26,12 +26,12 @@ module RuntimeProfiler
|
|
26
26
|
|
27
27
|
@data[key][:sql] = event.sanitized_sql
|
28
28
|
@data[key][:runtimes] = [
|
29
|
-
[
|
29
|
+
[event.total_runtime, event.trace.first]
|
30
30
|
]
|
31
31
|
end
|
32
32
|
|
33
33
|
def update(event)
|
34
|
-
@data[event.key][:runtimes] << [
|
34
|
+
@data[event.key][:runtimes] << [event.total_runtime, event.trace.first]
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
data/lib/runtime_profiler/cli.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module RuntimeProfiler
|
2
|
-
class
|
2
|
+
class Data
|
3
3
|
attr_reader :controller_data, :sql_data
|
4
4
|
|
5
5
|
def initialize(controller_data: nil, sql_data: nil)
|
@@ -8,14 +8,36 @@ module RuntimeProfiler
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def persist!
|
11
|
-
|
11
|
+
File.open(output_file, 'w') do |f|
|
12
|
+
f.write JSON.dump(profiling_data)
|
13
|
+
end
|
14
|
+
|
15
|
+
puts "\n"
|
16
|
+
puts 'Profiling data written at ' + output_file.to_s
|
17
|
+
puts 'You can view profiling data via: bundle exec runtime_profiler view ' + output_file.to_s
|
18
|
+
puts "\n"
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def output_file
|
24
|
+
FileUtils.mkdir_p(RuntimeProfiler.output_path)
|
25
|
+
filename = ['rp', Process.pid, Time.now.to_i].join('-') + '.json'
|
26
|
+
File.join(RuntimeProfiler.output_path, filename)
|
27
|
+
end
|
28
|
+
|
29
|
+
def profiled_api
|
30
|
+
return unless controller_data[:payload]
|
31
|
+
@profiled_api ||= [
|
12
32
|
controller_data[:payload][:controller],
|
13
33
|
controller_data[:payload][:action]
|
14
|
-
].join('#')
|
34
|
+
].join('#')
|
35
|
+
end
|
15
36
|
|
16
|
-
|
17
|
-
|
18
|
-
|
37
|
+
def profiling_data
|
38
|
+
@profiling_data ||= {
|
39
|
+
profiling: {
|
40
|
+
profiled_api: profiled_api,
|
19
41
|
summary: {
|
20
42
|
db_runtime: controller_data[:db_runtime],
|
21
43
|
view_runtime: controller_data[:view_runtime],
|
@@ -27,59 +49,34 @@ module RuntimeProfiler
|
|
27
49
|
slowest_method: method_calls_data[:slowest_method],
|
28
50
|
mostly_called_method: method_calls_data[:mostly_called_method]
|
29
51
|
},
|
30
|
-
|
31
|
-
|
32
|
-
|
52
|
+
profiled_sql_calls: sql_calls_data[:profiled_sql_calls],
|
53
|
+
profiled_methods: method_calls_data[:profiled_methods],
|
54
|
+
profiled_at: Time.now
|
33
55
|
}
|
34
56
|
}
|
35
|
-
|
36
|
-
FileUtils.mkdir_p(RuntimeProfiler.output_path)
|
37
|
-
filename = ['runtime-profiling', Process.pid, Time.now.to_i].join('-') << '.json'
|
38
|
-
output_file = File.join(RuntimeProfiler.output_path, filename)
|
39
|
-
|
40
|
-
File.open(output_file, 'w') do |f|
|
41
|
-
f.write JSON.dump(instrumentation_data)
|
42
|
-
end
|
43
|
-
|
44
|
-
puts "\n"
|
45
|
-
puts '~~~~> [ Profiling RUNTIME ] Profiling now COMPLETE and JSON report is written at ' + output_file.to_s
|
46
|
-
puts '~~~~> [ Profiling RUNTIME ]'
|
47
|
-
puts '~~~~> [ Profiling RUNTIME ] You can do the following to view the JSON report in console:'
|
48
|
-
puts '~~~~> [ Profiling RUNTIME ]'
|
49
|
-
puts '~~~~> [ Profiling RUNTIME ] bundle exec runtime_profiler view ' + output_file.to_s
|
50
|
-
puts '~~~~> [ Profiling RUNTIME ]'
|
51
|
-
puts '~~~~> [ Profiling RUNTIME ] Or'
|
52
|
-
puts '~~~~> [ Profiling RUNTIME ]'
|
53
|
-
puts '~~~~> [ Profiling RUNTIME ] bundle exec runtime_profiler view --help'
|
54
|
-
puts '~~~~> [ Profiling RUNTIME ]'
|
55
|
-
puts '~~~~> [ Profiling RUNTIME ] for more details.'
|
56
57
|
end
|
57
58
|
|
58
|
-
private
|
59
|
-
|
60
59
|
def method_calls_data
|
61
60
|
@method_calls_data ||= begin
|
62
|
-
|
61
|
+
profiled_methods = {}
|
63
62
|
|
64
63
|
# TODO: Group methods under a key and under an object
|
65
|
-
MethodMeter.measurement.
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
instrumented_methods[object.first] = [d]
|
77
|
-
end
|
64
|
+
MethodMeter.measurement.each_pair do |key, data|
|
65
|
+
data.each do |d|
|
66
|
+
object = d[:method].split(separator = '.')
|
67
|
+
object = d[:method].split(separator = '#') if object.length == 1
|
68
|
+
|
69
|
+
d[:method] = separator + object.second
|
70
|
+
|
71
|
+
if profiled_methods[object.first]
|
72
|
+
profiled_methods[object.first] << d
|
73
|
+
else
|
74
|
+
profiled_methods[object.first] = [d]
|
78
75
|
end
|
79
76
|
end
|
80
77
|
end
|
81
78
|
|
82
|
-
|
79
|
+
profiled_methods = profiled_methods.inject({}) do |hash, (key, value)|
|
83
80
|
val = value.sort { |a, b| b[:total_runtime] <=> a[:total_runtime] }
|
84
81
|
hash[key] = val
|
85
82
|
hash
|
@@ -88,7 +85,7 @@ module RuntimeProfiler
|
|
88
85
|
slowest_method = {total_runtime: 0}
|
89
86
|
mostly_called_method = {total_calls: 0}
|
90
87
|
|
91
|
-
|
88
|
+
profiled_methods.each do |profiled_object_name, methods|
|
92
89
|
# sort using `total_runtime` in DESC order
|
93
90
|
_methods = methods.sort { |a, b| b[:total_runtime] <=> a[:total_runtime] }
|
94
91
|
slowest = _methods[0]
|
@@ -111,7 +108,7 @@ module RuntimeProfiler
|
|
111
108
|
end
|
112
109
|
|
113
110
|
{
|
114
|
-
|
111
|
+
profiled_methods: profiled_methods,
|
115
112
|
slowest_method: slowest_method,
|
116
113
|
mostly_called_method: mostly_called_method
|
117
114
|
}
|
@@ -120,7 +117,7 @@ module RuntimeProfiler
|
|
120
117
|
|
121
118
|
def sql_calls_data
|
122
119
|
@sql_calls_data ||= begin
|
123
|
-
|
120
|
+
profiled_sql_calls = []
|
124
121
|
|
125
122
|
slowest_sql = {total_runtime: 0}
|
126
123
|
mostly_called_sql = {total_calls: 0}
|
@@ -141,7 +138,7 @@ module RuntimeProfiler
|
|
141
138
|
slowest_sql[:source] = slowest[1]
|
142
139
|
end
|
143
140
|
|
144
|
-
|
141
|
+
profiled_sql_calls << {
|
145
142
|
sql: value[:sql],
|
146
143
|
runtimes: runtimes,
|
147
144
|
total_calls: total_calls,
|
@@ -163,9 +160,9 @@ module RuntimeProfiler
|
|
163
160
|
end
|
164
161
|
|
165
162
|
{
|
166
|
-
|
167
|
-
total_sql_calls:
|
168
|
-
total_unique_sql_calls:
|
163
|
+
profiled_sql_calls: profiled_sql_calls.sort { |a, b| b[:max] <=> a[:max] },
|
164
|
+
total_sql_calls: profiled_sql_calls.map { |sql_call| sql_call[:total_calls] }.reduce(:+),
|
165
|
+
total_unique_sql_calls: profiled_sql_calls.size,
|
169
166
|
slowest_sql: slowest_sql,
|
170
167
|
mostly_called_sql: mostly_called_sql
|
171
168
|
}
|
@@ -10,8 +10,8 @@ module RuntimeProfiler
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def recordable?
|
13
|
-
return true unless RuntimeProfiler.
|
14
|
-
|
13
|
+
return true unless RuntimeProfiler.profiled_sql_commands.respond_to?(:join)
|
14
|
+
profiled_sql_matcher =~ sql
|
15
15
|
end
|
16
16
|
|
17
17
|
def total_runtime
|
@@ -41,12 +41,12 @@ module RuntimeProfiler
|
|
41
41
|
@sql ||= @payload[:sql].dup
|
42
42
|
end
|
43
43
|
|
44
|
-
def
|
45
|
-
@
|
44
|
+
def profiled_sql_matcher
|
45
|
+
@profiled_sql_matcher ||= /\A#{RuntimeProfiler.profiled_sql_commands.join('|')}/i
|
46
46
|
end
|
47
47
|
|
48
48
|
def trace_path_matcher
|
49
|
-
@trace_path_matcher ||= %r{^(#{RuntimeProfiler.
|
49
|
+
@trace_path_matcher ||= %r{^(#{RuntimeProfiler.profiled_paths.join('|')})\/}
|
50
50
|
end
|
51
51
|
|
52
52
|
def sanitize_trace!(trace)
|
@@ -59,7 +59,7 @@ module RuntimeProfiler
|
|
59
59
|
|
60
60
|
Rails.backtrace_cleaner.remove_silencers!
|
61
61
|
|
62
|
-
if RuntimeProfiler.
|
62
|
+
if RuntimeProfiler.profiled_paths.respond_to?(:join)
|
63
63
|
Rails.backtrace_cleaner.add_silencer do |line|
|
64
64
|
line !~ trace_path_matcher
|
65
65
|
end
|
@@ -1,55 +1,64 @@
|
|
1
|
-
|
1
|
+
require 'method_meter'
|
2
2
|
|
3
3
|
require 'runtime_profiler/callbacks/active_record'
|
4
4
|
require 'runtime_profiler/callbacks/action_controller'
|
5
|
-
require 'runtime_profiler/
|
5
|
+
require 'runtime_profiler/data'
|
6
6
|
|
7
7
|
module RuntimeProfiler
|
8
8
|
class Profiler
|
9
|
-
attr_accessor :
|
9
|
+
attr_accessor :profiled_constants
|
10
10
|
|
11
|
-
def initialize(
|
12
|
-
self.
|
11
|
+
def initialize(constants)
|
12
|
+
self.profiled_constants = constants
|
13
|
+
prepare_for_profiling
|
13
14
|
end
|
14
15
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
16
|
+
def profile!(key)
|
17
|
+
MethodMeter.measure!(key) { yield }
|
18
|
+
save_profiling_data
|
18
19
|
end
|
19
20
|
|
20
|
-
|
21
|
+
private
|
22
|
+
|
23
|
+
def prepare_for_profiling
|
24
|
+
subscribe_to_rails_event_notifications
|
25
|
+
prepare_methods_to_profile
|
26
|
+
end
|
27
|
+
|
28
|
+
def subscribe_to_rails_event_notifications
|
21
29
|
@subscribers = []
|
22
30
|
|
23
31
|
@active_record_callback = Callback::ActiveRecord.new
|
24
32
|
|
25
33
|
@subscribers << ActiveSupport::Notifications
|
26
|
-
|
34
|
+
.subscribe('sql.active_record', @active_record_callback)
|
27
35
|
|
28
36
|
@action_controller_callback = Callback::ActionController.new
|
29
37
|
|
30
38
|
@subscribers << ActiveSupport::Notifications
|
31
|
-
|
39
|
+
.subscribe('process_action.action_controller', @action_controller_callback)
|
32
40
|
end
|
33
41
|
|
34
|
-
def
|
42
|
+
def unsubscribe_to_rails_event_notifications
|
35
43
|
@subscribers.each do |subscriber|
|
36
44
|
ActiveSupport::Notifications.unsubscribe(subscriber)
|
37
45
|
end
|
38
46
|
end
|
39
47
|
|
40
|
-
def
|
41
|
-
|
42
|
-
.
|
48
|
+
def prepare_methods_to_profile
|
49
|
+
profiled_constants
|
50
|
+
.flatten
|
51
|
+
.each { |constant| MethodMeter.observe(constant, RuntimeProfiler.excepted_methods) }
|
43
52
|
end
|
44
53
|
|
45
|
-
def
|
46
|
-
|
54
|
+
def save_profiling_data
|
55
|
+
unsubscribe_to_rails_event_notifications
|
47
56
|
|
48
|
-
|
49
|
-
|
50
|
-
|
57
|
+
profiling_data = RuntimeProfiler::Data.new \
|
58
|
+
controller_data: @action_controller_callback.controller_data,
|
59
|
+
sql_data: @active_record_callback.data
|
51
60
|
|
52
|
-
|
61
|
+
profiling_data.persist!
|
53
62
|
end
|
54
63
|
end
|
55
|
-
end
|
64
|
+
end
|
@@ -26,12 +26,12 @@ module RuntimeProfiler
|
|
26
26
|
\e[1mSQL CALLS\e[22m
|
27
27
|
Total : %s
|
28
28
|
Total Unique : %s
|
29
|
-
|
29
|
+
|
30
30
|
\e[1mSLOWEST\e[22m
|
31
31
|
Total Runtime : %s ms
|
32
32
|
SQL : %s
|
33
33
|
Source : %s
|
34
|
-
|
34
|
+
|
35
35
|
\e[1mMOSTLY CALLED\e[22m
|
36
36
|
Total Calls : %s
|
37
37
|
Total Runtime : %s ms
|
@@ -86,7 +86,7 @@ module RuntimeProfiler
|
|
86
86
|
attr_accessor :data, :options
|
87
87
|
|
88
88
|
def initialize(json_file, options)
|
89
|
-
self.data = JSON.parse(
|
89
|
+
self.data = JSON.parse(File.read(json_file))
|
90
90
|
self.options = options
|
91
91
|
end
|
92
92
|
|
@@ -95,12 +95,12 @@ module RuntimeProfiler
|
|
95
95
|
|
96
96
|
if options.details == 'full'
|
97
97
|
if only_methods?
|
98
|
-
|
98
|
+
print_profiled_methods
|
99
99
|
elsif only_sqls?
|
100
|
-
|
100
|
+
print_profiled_sql_calls
|
101
101
|
else
|
102
|
-
|
103
|
-
|
102
|
+
print_profiled_methods
|
103
|
+
print_profiled_sql_calls
|
104
104
|
end
|
105
105
|
end
|
106
106
|
end
|
@@ -121,34 +121,34 @@ module RuntimeProfiler
|
|
121
121
|
|
122
122
|
def print_summary
|
123
123
|
summary = if only_methods?
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
124
|
+
METHODS_DETAILS_TEMPLATE % details_template_data
|
125
|
+
elsif only_sqls?
|
126
|
+
SQLS_DETAILS_TEMPLATE % details_template_data
|
127
|
+
else
|
128
|
+
FULL_DETAILS_TEMPLATE % details_template_data
|
129
|
+
end
|
130
130
|
puts summary
|
131
131
|
end
|
132
132
|
|
133
|
-
def
|
134
|
-
|
133
|
+
def print_profiled_methods
|
134
|
+
profiled_methods = []
|
135
135
|
|
136
|
-
data['
|
137
|
-
|
136
|
+
data['profiling']['profiled_methods'].each do |profiled_object_name, methods|
|
137
|
+
_profiled_methods = methods.map do |method|
|
138
138
|
method['method'] = [profiled_object_name, method['method']].join
|
139
139
|
method
|
140
140
|
end
|
141
|
-
|
141
|
+
profiled_methods.concat(_profiled_methods)
|
142
142
|
end
|
143
143
|
|
144
|
-
|
145
|
-
|
146
|
-
|
144
|
+
profiled_methods = runtime_above(profiled_methods) if options.runtime_above.presence > 0
|
145
|
+
profiled_methods = calls_above(profiled_methods) if options.calls_above.presence > 0
|
146
|
+
profiled_methods = sort(profiled_methods)
|
147
147
|
|
148
148
|
table = Terminal::Table.new do |t|
|
149
149
|
t.headings = ['Method', 'Total Runtime (ms)', 'Total Calls', 'Min (ms)', 'Max (ms)']
|
150
150
|
|
151
|
-
|
151
|
+
profiled_methods.each_with_index do |row, index|
|
152
152
|
t.add_row [
|
153
153
|
row['method'],
|
154
154
|
row['total_runtime'].round(rounding),
|
@@ -156,29 +156,28 @@ module RuntimeProfiler
|
|
156
156
|
row['min'].round(rounding),
|
157
157
|
row['max'].round(rounding)
|
158
158
|
]
|
159
|
-
t.add_separator if index <
|
159
|
+
t.add_separator if index < profiled_methods.size - 1
|
160
160
|
end
|
161
|
-
|
162
161
|
end
|
163
162
|
|
164
163
|
puts
|
165
164
|
puts
|
166
|
-
puts "\e[
|
165
|
+
puts "\e[1mPROFILED METHOD(s)\e[22m"
|
167
166
|
puts
|
168
167
|
puts table
|
169
168
|
end
|
170
169
|
|
171
|
-
def
|
172
|
-
|
170
|
+
def print_profiled_sql_calls
|
171
|
+
profiled_sql_calls = data['profiling']['profiled_sql_calls']
|
173
172
|
|
174
|
-
|
175
|
-
|
176
|
-
|
173
|
+
profiled_sql_calls = runtime_above(profiled_sql_calls) if options.runtime_above.presence > 0
|
174
|
+
profiled_sql_calls = calls_above(profiled_sql_calls) if options.calls_above.presence > 0
|
175
|
+
profiled_sql_calls = sort(profiled_sql_calls, false)
|
177
176
|
|
178
177
|
table = Terminal::Table.new do |t|
|
179
|
-
t.headings = ['Count', 'Total Runtime (ms)', 'Average Runtime (ms)', '
|
178
|
+
t.headings = ['SQL Query', 'Count', 'Total Runtime (ms)', 'Average Runtime (ms)', 'Source']
|
180
179
|
|
181
|
-
|
180
|
+
profiled_sql_calls.each_with_index do |row, index|
|
182
181
|
chopped_sql = wrap_text(row['sql'], sql_width)
|
183
182
|
source_list = wrap_list(row['runtimes'].map { |runtime| runtime[1] }.uniq, sql_width - 15)
|
184
183
|
average_runtime = row['average'].round(rounding)
|
@@ -192,29 +191,29 @@ module RuntimeProfiler
|
|
192
191
|
(0...total_lines).each do |line|
|
193
192
|
count = line == 0 ? row['total_calls'] : ''
|
194
193
|
average = line == 0 ? average_runtime : ''
|
195
|
-
total_runtime = line == 0 ? total_runtime
|
194
|
+
total_runtime = line == 0 ? total_runtime : ''
|
196
195
|
source = source_list.length > line ? source_list[line] : ''
|
197
196
|
query = row['sql'].length > line ? chopped_sql[line] : ''
|
198
197
|
|
199
198
|
t.add_row []
|
200
|
-
t.add_row [count, total_runtime, average,
|
199
|
+
t.add_row [query, count, total_runtime, average, source]
|
201
200
|
end
|
202
201
|
|
203
202
|
t.add_row []
|
204
|
-
t.add_separator if index <
|
203
|
+
t.add_separator if index < profiled_sql_calls.size - 1
|
205
204
|
end
|
206
|
-
|
207
205
|
end
|
208
206
|
|
209
207
|
puts
|
210
208
|
puts
|
211
|
-
puts "\e[
|
209
|
+
puts "\e[1mPROFILED SQL(s)\e[22m"
|
212
210
|
puts
|
213
211
|
puts table
|
214
212
|
end
|
215
213
|
|
216
214
|
def wrap_text(text, width)
|
217
215
|
return [text] if text.length <= width
|
216
|
+
|
218
217
|
text.scan(/.{1,#{width}}/)
|
219
218
|
end
|
220
219
|
|
@@ -231,7 +230,7 @@ module RuntimeProfiler
|
|
231
230
|
end
|
232
231
|
end
|
233
232
|
|
234
|
-
def sort(data, methods=true)
|
233
|
+
def sort(data, methods = true)
|
235
234
|
if methods
|
236
235
|
data.sort_by do |d|
|
237
236
|
if options.sort_by == 'max_runtime'
|
@@ -243,8 +242,8 @@ module RuntimeProfiler
|
|
243
242
|
end
|
244
243
|
end
|
245
244
|
else
|
246
|
-
|
247
|
-
data.sort_by { |d| options.sort_by == 'total_runtime' ?
|
245
|
+
options.sort_by = 'total_runtime' if options.sort_by == 'max_runtime'
|
246
|
+
data.sort_by { |d| options.sort_by == 'total_runtime' ? -d['total_runtime'] : -d['total_calls'] }
|
248
247
|
end
|
249
248
|
end
|
250
249
|
|
@@ -257,7 +256,7 @@ module RuntimeProfiler
|
|
257
256
|
end
|
258
257
|
|
259
258
|
def details_template_data
|
260
|
-
summary = data['
|
259
|
+
summary = data['profiling']['summary']
|
261
260
|
|
262
261
|
template_data = [
|
263
262
|
summary['total_runtime'] ? summary['total_runtime'].round(rounding) : 'n/a',
|
data/rp_view_command.png
ADDED
Binary file
|
data/runtime_profiler.gemspec
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
3
|
require 'runtime_profiler/version'
|
5
4
|
|
@@ -19,15 +18,16 @@ Gem::Specification.new do |spec|
|
|
19
18
|
spec.executables = ['runtime_profiler']
|
20
19
|
spec.require_paths = ['lib']
|
21
20
|
|
21
|
+
spec.add_development_dependency 'activesupport', '>= 3.0.0'
|
22
22
|
spec.add_development_dependency 'bundler'
|
23
|
-
spec.add_development_dependency 'rake', '>= 12.3.3'
|
24
23
|
spec.add_development_dependency 'minitest', '~> 5.0'
|
25
|
-
spec.add_development_dependency 'activesupport', '>= 3.0.0'
|
26
|
-
spec.add_development_dependency 'pry'
|
27
24
|
spec.add_development_dependency 'minitest-line'
|
28
|
-
spec.
|
25
|
+
spec.add_development_dependency 'pry'
|
26
|
+
spec.add_development_dependency 'rake', '>= 12.3.3'
|
27
|
+
spec.add_development_dependency 'rubocop'
|
29
28
|
spec.add_runtime_dependency 'commander'
|
30
|
-
spec.add_runtime_dependency 'hirb'
|
31
|
-
spec.add_runtime_dependency 'method_meter'
|
32
29
|
spec.add_runtime_dependency 'defined_methods'
|
30
|
+
spec.add_runtime_dependency 'hirb'
|
31
|
+
spec.add_runtime_dependency 'method_meter', '>= 0.4.3'
|
32
|
+
spec.add_runtime_dependency 'terminal-table'
|
33
33
|
end
|
metadata
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: runtime_profiler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wilfrido T. Nuqui Jr.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-02-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: activesupport
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 3.0.0
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 3.0.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: minitest
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,19 +53,19 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '5.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: minitest-line
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: '0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
68
|
+
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: pry
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -81,7 +81,21 @@ dependencies:
|
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 12.3.3
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 12.3.3
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
86
100
|
requirements:
|
87
101
|
- - ">="
|
@@ -95,7 +109,7 @@ dependencies:
|
|
95
109
|
- !ruby/object:Gem::Version
|
96
110
|
version: '0'
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
112
|
+
name: commander
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
100
114
|
requirements:
|
101
115
|
- - ">="
|
@@ -109,7 +123,7 @@ dependencies:
|
|
109
123
|
- !ruby/object:Gem::Version
|
110
124
|
version: '0'
|
111
125
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
126
|
+
name: defined_methods
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
114
128
|
requirements:
|
115
129
|
- - ">="
|
@@ -142,16 +156,16 @@ dependencies:
|
|
142
156
|
requirements:
|
143
157
|
- - ">="
|
144
158
|
- !ruby/object:Gem::Version
|
145
|
-
version:
|
159
|
+
version: 0.4.3
|
146
160
|
type: :runtime
|
147
161
|
prerelease: false
|
148
162
|
version_requirements: !ruby/object:Gem::Requirement
|
149
163
|
requirements:
|
150
164
|
- - ">="
|
151
165
|
- !ruby/object:Gem::Version
|
152
|
-
version:
|
166
|
+
version: 0.4.3
|
153
167
|
- !ruby/object:Gem::Dependency
|
154
|
-
name:
|
168
|
+
name: terminal-table
|
155
169
|
requirement: !ruby/object:Gem::Requirement
|
156
170
|
requirements:
|
157
171
|
- - ">="
|
@@ -185,12 +199,13 @@ files:
|
|
185
199
|
- lib/runtime_profiler/callbacks/action_controller.rb
|
186
200
|
- lib/runtime_profiler/callbacks/active_record.rb
|
187
201
|
- lib/runtime_profiler/cli.rb
|
202
|
+
- lib/runtime_profiler/data.rb
|
188
203
|
- lib/runtime_profiler/events/process_action_event.rb
|
189
204
|
- lib/runtime_profiler/events/sql_event.rb
|
190
|
-
- lib/runtime_profiler/instrumentation_data.rb
|
191
205
|
- lib/runtime_profiler/profiler.rb
|
192
206
|
- lib/runtime_profiler/text_report.rb
|
193
207
|
- lib/runtime_profiler/version.rb
|
208
|
+
- rp_view_command.png
|
194
209
|
- runtime_profiler.gemspec
|
195
210
|
homepage: http://www.github.com/wnuqui/runtime_profiler
|
196
211
|
licenses:
|