runtime_profiler 0.1.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: f0d303faf46e8b0eec88384965d29c5136390166
4
- data.tar.gz: 1653ba57d2cbf4b10dd9c8da35310e2ec6d20f00
2
+ SHA256:
3
+ metadata.gz: cfc4edf1ad5783057cfd0760d11e49a8d91665b71c33b6e809a0796a7d51cb83
4
+ data.tar.gz: cb19a035d46cae60fd4d16c626fac305a47fab4aaf28134feb47883a9f8a5d94
5
5
  SHA512:
6
- metadata.gz: ecdf3e02b864efeec7f8dacc7b96c443c6baaa39a4931af40cb3d720d6519f5094b2f2f10cdb884cc3f3ecb305440b59f4dfe968df9aeb92e3900536fa8814b6
7
- data.tar.gz: 26fe71304d4b511468385739f163033f9bc7ec3562168d844068aac47fcb4bd9c540ade25b7cf6dd5ebac05d8184965f740689f4f5d27ebde1eecd1cc1b48a65
6
+ metadata.gz: 1d85ca30cfa19fab9739ca87d6fb0fd1e4cddcf43ed0c5af8c1eab7d867ab24e5dfb11c4e0ad37cbac48a5bf5c149214e40899bd5e4e0a5555bef22d68efd60f
7
+ data.tar.gz: 6f3ab5d14e1cb231bd4c55156270190346d1de5f1aac3a4c3b09ca992ec577e105b35197a24022f63687034bb07df2f168249a6f72b5adb3ee8a97d4e79282d2
@@ -0,0 +1,18 @@
1
+ version: v1.0
2
+ name: Ruby
3
+ agent:
4
+ machine:
5
+ type: e1-standard-2
6
+ os_image: ubuntu1804
7
+ blocks:
8
+ - name: test
9
+ task:
10
+ jobs:
11
+ - name: test
12
+ commands:
13
+ - checkout
14
+ - sem-version ruby 2.6.5
15
+ - cache restore
16
+ - bundle install --path vendor/bundle
17
+ - cache store
18
+ - bundle exec rake
data/README.md CHANGED
@@ -1,20 +1,36 @@
1
- # runtime_profiler - Runtime Profiler for Rails Applications
1
+ # runtime_profiler
2
2
 
3
- `runtime_profiler` instruments api or a method of your Rails application using Rails' `ActiveSupport::Notifications`
3
+ *A runtime profiler for Rails applications.*
4
4
 
5
- It then aggregates and generates report to give you insights about specific calls in your Rails application.
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
- ## Note
7
+ [![Build Status](https://wnuqui.semaphoreci.com/badges/runtime_profiler/branches/master.svg?style=shields)](https://wnuqui.semaphoreci.com/projects/runtime_profiler)
8
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.
9
+ ## Table of contents
10
10
 
11
- ## Installation
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
12
28
 
13
29
  Add this line to your application's Gemfile:
14
30
 
15
31
  ```ruby
32
+ # In your Gemfile
16
33
  group :development, :test do
17
- ... ...
18
34
  gem 'runtime_profiler'
19
35
  end
20
36
  ```
@@ -23,13 +39,33 @@ And then execute:
23
39
 
24
40
  $ bundle
25
41
 
26
- ## Profiling/Instrumenting
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).
51
+
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
27
62
 
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.
63
+ You can make a test that targets a particular endpoint (or even just a method) and use `RuntimeProfiler.profile!` method in the test.
29
64
 
30
- Example:
31
65
  ```ruby
32
66
  it 'updates user' do
67
+ # Profiles runtime of PUT /users/:id endpoint and
68
+ # specifically interested with the methods of `User` model.
33
69
  RuntimeProfiler.profile!('updates user', [User]) {
34
70
  patch :update, { id: user.id, name: 'Joe' }
35
71
  }
@@ -38,19 +74,79 @@ it 'updates user' do
38
74
  end
39
75
  ```
40
76
 
41
- Run tests as usual and follow printed instructions after running tests.
77
+ Run the test as usual and follow printed instructions after running.
78
+
79
+ Or if you prefer writing just code snippet, then just wrap the snippet with `RuntimeProfiler.profile!` method:
80
+ ```ruby
81
+ # Profiles runtime of `UserMailer` mailer.
82
+ RuntimeProfiler.profile!('UserMailer', [UserMailer]) {
83
+ user = User.last
84
+ UserMailer.with(user: user).weekly_summary.deliver_now
85
+ }
86
+ ```
87
+
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.
89
+
90
+ ### Viewing Profiling Result
91
+
92
+ To see profiling report, you can open the report in browser with JSON viewer report. Or you can run the following command:
93
+
94
+ ```bash
95
+ bundle exec runtime_profiler view tmp/rp-124094-1608308786.json
96
+ ```
42
97
 
43
- ## Reporting
98
+ #### view options
44
99
 
45
- To see profiling/instrumenting report, please open the report in browser with JSON viewer report. Or you can run the following command:
100
+ Here are the command line options for `runtime_profiler view` command.
46
101
 
47
102
  ```bash
48
- bundle exec runtime_profiler view ~/the-rails-app/tmp/runtime-profiling-51079-1521371428.json
103
+ $ bundle exec runtime_profiler view --help
104
+
105
+ NAME:
106
+
107
+ view
108
+
109
+ SYNOPSIS:
110
+
111
+ runtime_profiler view <profile.report.json> [options]
112
+
113
+ DESCRIPTION:
114
+
115
+ Display report in console given the JSON report file
116
+
117
+ OPTIONS:
118
+
119
+ --sort-by COLUMN
120
+ Sort by COLUMN. COLUMN can be "max_runtime", total_calls" or "total_runtime". Default is "max_runtime".
121
+
122
+ --details TYPE
123
+ TYPE can be "full" or "summary". Default is "summary"
124
+
125
+ --only-sqls
126
+ Show only SQL queries. Default is false.
127
+
128
+ --only-methods
129
+ Show only methods. Default is false.
130
+
131
+ --runtime-above RUNTIME
132
+ RUNTIME is integer or float value in ms.
133
+
134
+ --calls-above CALLS
135
+ CALLS is integer value.
136
+
137
+ --rounding ROUNDING
138
+ ROUNDING is integer value. Used in rounding runtimes. Default is 4.
49
139
  ```
50
140
 
51
- ## Configurations
141
+ ### Screenshot
142
+
143
+ <p align="center">
144
+ <img src="rp_view_only_methods_full_details.png">
145
+ </p>
146
+
147
+ ### Configurations
52
148
 
53
- 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`.
54
150
  ```ruby
55
151
  RuntimeProfiler.output_path = File.join(Rails.root.to_s, 'tmp')
56
152
  RuntimeProfiler.instrumented_constants = [User]
@@ -11,24 +11,32 @@ module RuntimeProfiler
11
11
  end
12
12
 
13
13
  config_accessor :instrumented_paths do
14
- %w(app lib)
14
+ %w[app lib]
15
15
  end
16
16
 
17
17
  config_accessor :instrumented_sql_commands do
18
- %w(SELECT INSERT UPDATE DELETE)
18
+ %w[SELECT INSERT UPDATE DELETE]
19
19
  end
20
20
 
21
21
  config_accessor :output_path do
22
- if defined?(Rails) && Rails.respond_to?(:root)
22
+ if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
23
23
  File.join(Rails.root.to_s, 'tmp')
24
24
  else
25
25
  'tmp'
26
26
  end
27
27
  end
28
28
 
29
+ config_accessor :excepted_methods do
30
+ []
31
+ end
32
+
29
33
  class << self
30
34
  def configure
31
- Rails.application.eager_load! rescue nil
35
+ begin
36
+ Rails.application.eager_load!
37
+ rescue StandardError
38
+ nil
39
+ end
32
40
  yield self if block_given?
33
41
  end
34
42
 
@@ -41,17 +49,5 @@ module RuntimeProfiler
41
49
 
42
50
  profiler.save_instrumentation_data
43
51
  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
52
  end
57
- end
53
+ end
@@ -12,6 +12,7 @@ module RuntimeProfiler
12
12
  def call(*args)
13
13
  event = RuntimeProfiler::ProcessActionEvent.new(args: args)
14
14
  return unless event.recordable?
15
+
15
16
  add event
16
17
  end
17
18
 
@@ -26,12 +26,12 @@ module RuntimeProfiler
26
26
 
27
27
  @data[key][:sql] = event.sanitized_sql
28
28
  @data[key][:runtimes] = [
29
- [ event.total_runtime, event.trace.first ]
29
+ [event.total_runtime, event.trace.first]
30
30
  ]
31
31
  end
32
32
 
33
33
  def update(event)
34
- @data[event.key][:runtimes] << [ event.total_runtime, event.trace.first ]
34
+ @data[event.key][:runtimes] << [event.total_runtime, event.trace.first]
35
35
  end
36
36
  end
37
37
  end
@@ -16,32 +16,37 @@ module RuntimeProfiler
16
16
  c.syntax = 'runtime_profiler view <profile.report.json> [options]'
17
17
  c.description = 'Display report in console given the JSON report file'
18
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.'
19
+ c.option '--sort-by COLUMN', String, 'Sort by COLUMN. COLUMN can be "max_runtime", total_calls" or "total_runtime". Default is "max_runtime".'
20
+ c.option '--details TYPE', String, 'TYPE can be "full" or "summary". Default is "summary"'
21
+ c.option '--only-sqls', String, 'Show only SQL queries. Default is false.'
22
+ c.option '--only-methods', String, 'Show only methods. Default is false.'
23
+ c.option '--runtime-above RUNTIME', Float, 'RUNTIME is integer or float value in ms.'
24
+ c.option '--calls-above CALLS', Integer, 'CALLS is integer value.'
25
+ c.option '--rounding ROUNDING', Integer, 'ROUNDING is integer value. Used in rounding runtimes. Default is 4.'
25
26
 
26
27
  c.action do |args, options|
27
28
  default_options = {
28
- sort_by: 'total_calls',
29
+ sort_by: 'max_runtime',
29
30
  details: 'summary',
30
31
  runtime_above: 0,
31
32
  only_sqls: false,
32
33
  only_methods: false,
33
- calls_above: 1
34
+ calls_above: 0,
35
+ rounding: 4
34
36
  }
35
37
 
36
38
  options.default default_options
37
39
 
38
- report = RuntimeProfiler::TextReport.new(args.first, options)
39
-
40
- report.print
40
+ if args.first.nil?
41
+ say 'You need to supply <profile.report.json> as first argument of view.'
42
+ else
43
+ report = RuntimeProfiler::TextReport.new(args.first, options)
44
+ report.print
45
+ end
41
46
  end
42
47
  end
43
48
 
44
49
  run!
45
50
  end
46
51
  end
47
- end
52
+ end
@@ -30,4 +30,4 @@ module RuntimeProfiler
30
30
  @key ||= Digest::MD5.hexdigest(path.downcase)
31
31
  end
32
32
  end
33
- end
33
+ end
@@ -1,3 +1,5 @@
1
+ require 'digest'
2
+
1
3
  module RuntimeProfiler
2
4
  class SqlEvent
3
5
  attr_reader :started_at, :finished_at, :payload, :trace
@@ -3,14 +3,39 @@ module RuntimeProfiler
3
3
  attr_reader :controller_data, :sql_data
4
4
 
5
5
  def initialize(controller_data: nil, sql_data: nil)
6
- @controller_data = controller_data
6
+ @controller_data = controller_data || {}
7
7
  @sql_data = sql_data
8
8
  end
9
9
 
10
10
  def persist!
11
- instrumented_api = [controller_data[:payload][:controller], controller_data[:payload][:action]].join('#')
11
+ File.open(output_file, 'w') do |f|
12
+ f.write JSON.dump(instrumentation_data)
13
+ end
12
14
 
13
- instrumentation_data = {
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 instrumented_api
30
+ return unless controller_data[:payload]
31
+ @instrumented_api ||= [
32
+ controller_data[:payload][:controller],
33
+ controller_data[:payload][:action]
34
+ ].join('#')
35
+ end
36
+
37
+ def instrumentation_data
38
+ @instrumentation_data ||= {
14
39
  instrumentation: {
15
40
  instrumented_api: instrumented_api,
16
41
  summary: {
@@ -29,30 +54,8 @@ module RuntimeProfiler
29
54
  instrumented_at: Time.now
30
55
  }
31
56
  }
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
57
  end
53
58
 
54
- private
55
-
56
59
  def method_calls_data
57
60
  @method_calls_data ||= begin
58
61
  instrumented_methods = {}
@@ -23,12 +23,12 @@ module RuntimeProfiler
23
23
  @active_record_callback = Callback::ActiveRecord.new
24
24
 
25
25
  @subscribers << ActiveSupport::Notifications
26
- .subscribe('sql.active_record', @active_record_callback)
26
+ .subscribe('sql.active_record', @active_record_callback)
27
27
 
28
28
  @action_controller_callback = Callback::ActionController.new
29
29
 
30
30
  @subscribers << ActiveSupport::Notifications
31
- .subscribe('process_action.action_controller', @action_controller_callback)
31
+ .subscribe('process_action.action_controller', @action_controller_callback)
32
32
  end
33
33
 
34
34
  def unsubscribe_to_event_notifications
@@ -38,18 +38,18 @@ module RuntimeProfiler
38
38
  end
39
39
 
40
40
  def prepare_methods_to_instrument
41
- self.instrumented_constants.flatten
42
- .each { |constant| MethodMeter.observe(constant) }
41
+ instrumented_constants.flatten
42
+ .each { |constant| MethodMeter.observe(constant, RuntimeProfiler.excepted_methods) }
43
43
  end
44
44
 
45
45
  def save_instrumentation_data
46
46
  unsubscribe_to_event_notifications
47
47
 
48
48
  instrumentation_data = RuntimeProfiler::InstrumentationData.new \
49
- controller_data: @action_controller_callback.controller_data,
50
- sql_data: @active_record_callback.data
49
+ controller_data: @action_controller_callback.controller_data,
50
+ sql_data: @active_record_callback.data
51
51
 
52
52
  instrumentation_data.persist!
53
53
  end
54
54
  end
55
- end
55
+ end
@@ -1,6 +1,7 @@
1
1
  require 'hirb'
2
2
  require 'terminal-table'
3
3
  require 'active_support/core_ext/string'
4
+ require 'pry'
4
5
 
5
6
  module RuntimeProfiler
6
7
  class TextReport
@@ -8,29 +9,72 @@ module RuntimeProfiler
8
9
  DURATION_WIDTH = 22
9
10
  TOTAL_RUNTIME_WIDTH = 20
10
11
 
11
- SUMMARY_TEMPLATE = <<-EOT.strip_heredoc
12
+ FULL_DETAILS_TEMPLATE = <<-EOT.strip_heredoc
12
13
 
13
14
  \e[1mPROFILING REPORT\e[22m
14
15
  ----------------
15
16
 
16
- \e[1mRUNTIME\e[22m
17
+ \e[1mAPI RUNTIME\e[22m
17
18
  Total Runtime : %s ms
18
19
  Database Runtime : %s ms
19
20
  View Runtime : %s ms
20
21
 
21
- \e[1mMETHODS\e[22m
22
+ \e[1mMETHOD CALLS\e[22m
22
23
  SLOWEST : %s (%s ms)
23
24
  MOSTLY CALLED : %s (%s number of calls in %s ms)
24
25
 
25
26
  \e[1mSQL CALLS\e[22m
26
27
  Total : %s
27
28
  Total Unique : %s
28
-
29
+
30
+ \e[1mSLOWEST\e[22m
31
+ Total Runtime : %s ms
32
+ SQL : %s
33
+ Source : %s
34
+
35
+ \e[1mMOSTLY CALLED\e[22m
36
+ Total Calls : %s
37
+ Total Runtime : %s ms
38
+ SQL : %s
39
+ Sources : %s
40
+
41
+ EOT
42
+
43
+ METHODS_DETAILS_TEMPLATE = <<-EOT.strip_heredoc
44
+
45
+ \e[1mPROFILING REPORT\e[22m
46
+ ----------------
47
+
48
+ \e[1mAPI RUNTIME\e[22m
49
+ Total Runtime : %s ms
50
+ Database Runtime : %s ms
51
+ View Runtime : %s ms
52
+
53
+ \e[1mMETHOD CALLS\e[22m
54
+ SLOWEST : %s (%s ms)
55
+ MOSTLY CALLED : %s (%s number of calls in %s ms)
56
+
57
+ EOT
58
+
59
+ SQLS_DETAILS_TEMPLATE = <<-EOT.strip_heredoc
60
+
61
+ \e[1mPROFILING REPORT\e[22m
62
+ ----------------
63
+
64
+ \e[1mAPI RUNTIME\e[22m
65
+ Total Runtime : %s ms
66
+ Database Runtime : %s ms
67
+ View Runtime : %s ms
68
+
69
+ \e[1mSQL CALLS\e[22m
70
+ Total : %s
71
+ Total Unique : %s
72
+
29
73
  \e[1mSLOWEST\e[22m
30
74
  Total Runtime : %s ms
31
75
  SQL : %s
32
76
  Source : %s
33
-
77
+
34
78
  \e[1mMOSTLY CALLED\e[22m
35
79
  Total Calls : %s
36
80
  Total Runtime : %s ms
@@ -42,49 +86,78 @@ module RuntimeProfiler
42
86
  attr_accessor :data, :options
43
87
 
44
88
  def initialize(json_file, options)
45
- self.data = JSON.parse( File.read(json_file) )
46
- self.options = options
89
+ self.data = JSON.parse(File.read(json_file))
90
+ self.options = options
47
91
  end
48
92
 
49
93
  def print
50
94
  print_summary
51
95
 
52
- if self.options.details == 'full'
53
- print_instrumented_methods
54
- print_instrumented_sql_calls
96
+ if options.details == 'full'
97
+ if only_methods?
98
+ print_instrumented_methods
99
+ elsif only_sqls?
100
+ print_instrumented_sql_calls
101
+ else
102
+ print_instrumented_methods
103
+ print_instrumented_sql_calls
104
+ end
55
105
  end
56
106
  end
57
107
 
58
108
  private
59
109
 
110
+ def only_methods?
111
+ options.only_methods.present? && options.only_sqls.blank?
112
+ end
113
+
114
+ def only_sqls?
115
+ options.only_sqls.present? && options.only_methods.blank?
116
+ end
117
+
118
+ def rounding
119
+ options.rounding
120
+ end
121
+
60
122
  def print_summary
61
- summary = SUMMARY_TEMPLATE % summary_template_data
123
+ summary = if only_methods?
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
62
130
  puts summary
63
131
  end
64
132
 
65
133
  def print_instrumented_methods
66
134
  instrumented_methods = []
67
135
 
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}
136
+ data['instrumentation']['instrumented_methods'].each do |profiled_object_name, methods|
137
+ _instrumented_methods = methods.map do |method|
138
+ method['method'] = [profiled_object_name, method['method']].join
139
+ method
140
+ end
141
+ instrumented_methods.concat(_instrumented_methods)
70
142
  end
71
143
 
144
+ instrumented_methods = runtime_above(instrumented_methods) if options.runtime_above.presence > 0
145
+ instrumented_methods = calls_above(instrumented_methods) if options.calls_above.presence > 0
72
146
  instrumented_methods = sort(instrumented_methods)
73
147
 
74
148
  table = Terminal::Table.new do |t|
75
- t.headings = ['Method', 'Total Runtime (ms)', 'Total Calls', 'Min', 'Max']
149
+ t.headings = ['Method', 'Total Runtime (ms)', 'Total Calls', 'Min (ms)', 'Max (ms)']
76
150
 
77
151
  instrumented_methods.each_with_index do |row, index|
78
152
  t.add_row [
79
153
  row['method'],
80
- row['total_runtime'],
154
+ row['total_runtime'].round(rounding),
81
155
  row['total_calls'],
82
- row['min'],
83
- row['max']
156
+ row['min'].round(rounding),
157
+ row['max'].round(rounding)
84
158
  ]
85
159
  t.add_separator if index < instrumented_methods.size - 1
86
160
  end
87
-
88
161
  end
89
162
 
90
163
  puts
@@ -95,15 +168,20 @@ module RuntimeProfiler
95
168
  end
96
169
 
97
170
  def print_instrumented_sql_calls
98
- instrumented_sql_calls = sort(self.data['instrumentation']['instrumented_sql_calls'])
171
+ instrumented_sql_calls = data['instrumentation']['instrumented_sql_calls']
172
+
173
+ instrumented_sql_calls = runtime_above(instrumented_sql_calls) if options.runtime_above.presence > 0
174
+ instrumented_sql_calls = calls_above(instrumented_sql_calls) if options.calls_above.presence > 0
175
+ instrumented_sql_calls = sort(instrumented_sql_calls, false)
99
176
 
100
177
  table = Terminal::Table.new do |t|
101
- t.headings = ['Count', 'Total Runtime (ms)', 'Average Runtime (ms)', 'SQL Query', 'Source']
178
+ t.headings = ['SQL Query', 'Count', 'Total Runtime (ms)', 'Average Runtime (ms)', 'Source']
102
179
 
103
180
  instrumented_sql_calls.each_with_index do |row, index|
104
181
  chopped_sql = wrap_text(row['sql'], sql_width)
105
182
  source_list = wrap_list(row['runtimes'].map { |runtime| runtime[1] }.uniq, sql_width - 15)
106
- average_runtime = row['average'].round(2)
183
+ average_runtime = row['average'].round(rounding)
184
+ total_runtime = row['total_runtime'].round(rounding)
107
185
  total_lines = if chopped_sql.length >= source_list.length
108
186
  chopped_sql.length
109
187
  else
@@ -113,18 +191,17 @@ module RuntimeProfiler
113
191
  (0...total_lines).each do |line|
114
192
  count = line == 0 ? row['total_calls'] : ''
115
193
  average = line == 0 ? average_runtime : ''
116
- total_runtime = line == 0 ? row['total_runtime'] : ''
194
+ total_runtime = line == 0 ? total_runtime : ''
117
195
  source = source_list.length > line ? source_list[line] : ''
118
196
  query = row['sql'].length > line ? chopped_sql[line] : ''
119
197
 
120
198
  t.add_row []
121
- t.add_row [count, total_runtime, average, query, source]
199
+ t.add_row [query, count, total_runtime, average, source]
122
200
  end
123
-
201
+
124
202
  t.add_row []
125
203
  t.add_separator if index < instrumented_sql_calls.size - 1
126
204
  end
127
-
128
205
  end
129
206
 
130
207
  puts
@@ -136,6 +213,7 @@ module RuntimeProfiler
136
213
 
137
214
  def wrap_text(text, width)
138
215
  return [text] if text.length <= width
216
+
139
217
  text.scan(/.{1,#{width}}/)
140
218
  end
141
219
 
@@ -152,43 +230,72 @@ module RuntimeProfiler
152
230
  end
153
231
  end
154
232
 
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']
233
+ def sort(data, methods = true)
234
+ if methods
235
+ data.sort_by do |d|
236
+ if options.sort_by == 'max_runtime'
237
+ -d['max']
238
+ elsif options.sort_by == 'total_runtime'
239
+ -d['total_runtime']
240
+ elsif options.sort_by == 'total_calls'
241
+ -d['total_calls']
242
+ end
161
243
  end
244
+ else
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'] }
162
247
  end
163
248
  end
164
249
 
165
- def summary_template_data
166
- summary = self.data['instrumentation']['summary']
250
+ def runtime_above(data)
251
+ data.select { |d| d['total_runtime'] > options.runtime_above }
252
+ end
253
+
254
+ def calls_above(data)
255
+ data.select { |d| d['total_calls'] > options.calls_above }
256
+ end
257
+
258
+ def details_template_data
259
+ summary = data['instrumentation']['summary']
167
260
 
168
- [
169
- summary['total_runtime'].round(2),
170
- summary['db_runtime'].round(2),
171
- summary['view_runtime'].round(2),
261
+ template_data = [
262
+ summary['total_runtime'] ? summary['total_runtime'].round(rounding) : 'n/a',
263
+ summary['db_runtime'] ? summary['db_runtime'].round(rounding) : 'n/a',
264
+ summary['view_runtime'] ? summary['view_runtime'].round(rounding) : 'n/a'
265
+ ]
172
266
 
267
+ methods_data = [
173
268
  summary['slowest_method']['method'],
174
- summary['slowest_method']['total_runtime'].round(2),
269
+ summary['slowest_method']['total_runtime'].round(rounding),
175
270
 
176
271
  summary['mostly_called_method']['method'],
177
272
  summary['mostly_called_method']['total_calls'],
178
- summary['mostly_called_method']['total_runtime'].round(2),
273
+ summary['mostly_called_method']['total_runtime'].round(rounding)
274
+ ]
179
275
 
276
+ sqls_data = [
180
277
  summary['total_sql_calls'],
181
278
  summary['total_unique_sql_calls'],
182
279
 
183
- summary['slowest_sql']['total_runtime'].round(2),
280
+ summary['slowest_sql']['total_runtime'].round(rounding),
184
281
  summary['slowest_sql']['sql'],
185
282
  summary['slowest_sql']['source'],
186
283
 
187
284
  summary['mostly_called_sql']['total_calls'],
188
- summary['mostly_called_sql']['total_runtime'].round(2),
285
+ summary['mostly_called_sql']['total_runtime'].round(rounding),
189
286
  summary['mostly_called_sql']['sql'],
190
287
  summary['mostly_called_sql']['runtimes'].map { |runtime| runtime[1] }.uniq
191
288
  ]
289
+
290
+ if only_methods?
291
+ template_data.concat(methods_data)
292
+ elsif only_sqls?
293
+ template_data.concat(sqls_data)
294
+ else
295
+ template_data
296
+ .concat(methods_data)
297
+ .concat(sqls_data)
298
+ end
192
299
  end
193
300
  end
194
301
  end
@@ -1,3 +1,3 @@
1
1
  module RuntimeProfiler
2
- VERSION = '0.1.0'.freeze
2
+ VERSION = '0.4.1'.freeze
3
3
  end
@@ -1,5 +1,4 @@
1
- # coding: utf-8
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
 
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
21
  spec.add_development_dependency 'activesupport', '>= 3.0.0'
26
- spec.add_development_dependency 'pry'
22
+ spec.add_development_dependency 'bundler'
23
+ spec.add_development_dependency 'minitest', '~> 5.0'
27
24
  spec.add_development_dependency 'minitest-line'
28
- spec.add_runtime_dependency 'terminal-table'
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'
29
+ spec.add_runtime_dependency 'defined_methods'
30
30
  spec.add_runtime_dependency 'hirb'
31
31
  spec.add_runtime_dependency 'method_meter'
32
- spec.add_runtime_dependency 'defined_methods'
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.1.0
4
+ version: 0.4.1
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: 2018-03-18 00:00:00.000000000 Z
11
+ date: 2021-01-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: bundler
14
+ name: activesupport
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.12'
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: '1.12'
26
+ version: 3.0.0
27
27
  - !ruby/object:Gem::Dependency
28
- name: rake
28
+ name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
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: '10.0'
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: activesupport
56
+ name: minitest-line
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: 3.0.0
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: 3.0.0
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: minitest-line
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: terminal-table
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: commander
126
+ name: defined_methods
113
127
  requirement: !ruby/object:Gem::Requirement
114
128
  requirements:
115
129
  - - ">="
@@ -151,7 +165,7 @@ dependencies:
151
165
  - !ruby/object:Gem::Version
152
166
  version: '0'
153
167
  - !ruby/object:Gem::Dependency
154
- name: defined_methods
168
+ name: terminal-table
155
169
  requirement: !ruby/object:Gem::Requirement
156
170
  requirements:
157
171
  - - ">="
@@ -173,6 +187,7 @@ extensions: []
173
187
  extra_rdoc_files: []
174
188
  files:
175
189
  - ".gitignore"
190
+ - ".semaphore/semaphore.yml"
176
191
  - Gemfile
177
192
  - LICENSE.txt
178
193
  - README.md
@@ -190,6 +205,7 @@ files:
190
205
  - lib/runtime_profiler/profiler.rb
191
206
  - lib/runtime_profiler/text_report.rb
192
207
  - lib/runtime_profiler/version.rb
208
+ - rp_view_only_methods_full_details.png
193
209
  - runtime_profiler.gemspec
194
210
  homepage: http://www.github.com/wnuqui/runtime_profiler
195
211
  licenses:
@@ -210,8 +226,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
210
226
  - !ruby/object:Gem::Version
211
227
  version: '0'
212
228
  requirements: []
213
- rubyforge_project:
214
- rubygems_version: 2.6.13
229
+ rubygems_version: 3.0.3
215
230
  signing_key:
216
231
  specification_version: 4
217
232
  summary: Runtime Profiler for Rails Applications