runtime_profiler 0.1.0 → 0.4.1

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 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