rails_performance 0.9.0 → 0.9.4

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
2
  SHA256:
3
- metadata.gz: fe073c7658af65303c6efc61dbaa973edba22d21c2991e715d523b6f4c96dc5f
4
- data.tar.gz: bf7780ef55eab59d2974b990eba4e2afad26b78683f685fb8383b4df4d8ada30
3
+ metadata.gz: d6532a46e577225137d7fbd9baae88b245157d75c4091690f73209886b6c6c15
4
+ data.tar.gz: 7946da511a5e589a854af1bef20c642de207004c4f860353657c92c895e2c485
5
5
  SHA512:
6
- metadata.gz: b223eb60dc1dc48c64a577d39d2bf34073ff916d0b73ad5f507a74bedc0f9a2aa5af5cc33ec9bc063b12cc441a572f99d4e8c68362935f74f2f9084c6d8b6ca0
7
- data.tar.gz: 7b030743ca075f02c38b4448ec0c1ad369a09d094dc381aba5c834c40ee245ccfe9511c939fa7d08e0e42d253c9b06817d4b6bc7d80c6b9d5f8bab7835472e7e
6
+ metadata.gz: 7c5a8d54bb4670dfe84cd720410dc30cab1facadcbac0f6457a41c2954d8ab66726728ebff333f1778ebdbcb3525806b01594619a0bf054a9e9c8eb3dfd9ab72
7
+ data.tar.gz: b7c6cda16e208941173e3aeaa7d3f5cc282a79d9b4077645364e341b0b30ad169133db769744abb24eefb34b7357145492047cf4a7041b505544df758783836e
data/README.md CHANGED
@@ -1,8 +1,12 @@
1
1
  # Rails Performance
2
2
 
3
- Self-hosted tool to monitor the performance of your Ruby on Rails application.
3
+ [![Build Status](https://travis-ci.org/igorkasyanchuk/rails_performance.svg?branch=master)](https://travis-ci.org/igorkasyanchuk/rails_performance)
4
4
 
5
- This is **simple and free alternative** to New Relic, Datadog or other similar services.
5
+ A self-hosted tool to monitor the performance of your Ruby on Rails application.
6
+
7
+ This is **simple and free alternative** to the New Relic APM, Datadog or other similar services.
8
+
9
+ ![Demo](docs/rails_performance.gif)
6
10
 
7
11
  It allows you to track:
8
12
 
@@ -10,18 +14,32 @@ It allows you to track:
10
14
  - an average response time
11
15
  - the slowest controllers & actions
12
16
  - total duration of time spent per request, views rendering, DB
13
- - SQL queries, rendering log in recent requests
17
+ - SQL queries, rendering logs in "Recent Requests" section
14
18
  - simple 500-crashes reports
19
+ - track Sidekiq jobs performance
20
+ - works with Rails 4.2+ (and probably 4.1, 4.0 too)
15
21
 
16
- All data is stored in local Redis and not sent to 3rd party servers.
22
+ All data are stored in `local` Redis and not sent to any 3rd party servers.
17
23
 
18
24
  ## Production
19
25
 
20
- Gem is production-ready. At least on my applications with ~800 unique users per day it works well.
26
+ Gem is production-ready. At least on my 2 applications with ~800 unique users per day it works perfectly.
27
+
28
+ Just don't forget to protect performance dashboard with http basic auth or check of current_user.
21
29
 
22
30
  ## Usage
23
31
 
24
- Create `config/initializers/rails_performance.rb`
32
+ ```
33
+ 1. Add gem to the Gemfile (in appropriate group if needed)
34
+ 2. Start rails server
35
+ 3. Make a few requests to your app
36
+ 4. open localhost:3000/rails/performance
37
+ 5. Tune the configuration and deploy to production
38
+ ```
39
+
40
+ Default configulation is listed below. But you can overide it.
41
+
42
+ Create `config/initializers/rails_performance.rb` in your app:
25
43
 
26
44
  ```ruby
27
45
  RailsPerformance.setup do |config|
@@ -48,6 +66,12 @@ Add this line to your application's Gemfile:
48
66
 
49
67
  ```ruby
50
68
  gem 'rails_performance'
69
+
70
+ # or
71
+
72
+ group :development, :production do
73
+ gem 'rails_performance'
74
+ end
51
75
  ```
52
76
 
53
77
  And then execute:
@@ -59,16 +83,21 @@ You must also have installed Redis server, because this gem is storing data into
59
83
 
60
84
  After installation and configuration, start your Rails application, make a few requests, and open `https://localhost:3000/rails/performance` URL.
61
85
 
86
+ ## How it works
87
+
88
+ ![Schema](docs/rails_performance.png)
89
+
62
90
  ## Limitations
63
91
 
64
92
  - it doesn't track params of POST/PUT requests
65
- - it doesn't track SQL queries (at least for now)
66
93
  - it doesn't track Redis/ElasticSearch or other apps
67
94
  - it can't compare historical data
68
95
  - depending on your load you may need to reduce time of for how long you store data, because all calculations are done in memory and it could take some time for high-load apps
69
96
 
70
97
  ## Redis
71
98
 
99
+ Gem is using Redis. This is the only one dependency.
100
+
72
101
  All information is stored into Redis. The default expiration time is set to `config.duration` from the configuration.
73
102
 
74
103
  ## Development & Testing
@@ -78,7 +107,7 @@ Just clone the repo, setup dummy app (`rails db:migrate`).
78
107
  After this:
79
108
 
80
109
  - rails s
81
- - rake tests
110
+ - rake test
82
111
 
83
112
  Like a regular web development.
84
113
 
@@ -93,6 +122,7 @@ The idea of this gem grew from curriosity how many RPM my app receiving per day.
93
122
  ## TODO
94
123
 
95
124
  - documentation in Readme
125
+ - capture stacktrace of 500 errors and show in side panel
96
126
  - generator for initial config
97
127
  - CI for tests
98
128
  - time/zone config?
@@ -108,10 +138,17 @@ The idea of this gem grew from curriosity how many RPM my app receiving per day.
108
138
  - number of requests last 24 hours, hour, etc.
109
139
  - collect deprecation.rails
110
140
  - fix misspellings?
141
+ - show "loading banner" until jquery is loaded?
142
+ - better UI on smaller screens? Recent requests when URL's are long? Truncate with CSS?
143
+ - rules for highlighting durations? how many ms to show warning, alert
111
144
 
112
145
  ## Contributing
113
146
 
114
- You are welcome to contribute.
147
+ You are welcome to contribute. I've a big list of TODO.
148
+
149
+ ## Big thanks to contributors
150
+
151
+ - https://github.com/synth
115
152
 
116
153
  ## License
117
154
 
@@ -4,7 +4,7 @@ class RailsPerformanceController < BaseController
4
4
 
5
5
  if RailsPerformance.enabled
6
6
  def index
7
- @datasource = RP::DataSource.new(prepare_query)
7
+ @datasource = RP::DataSource.new(**prepare_query, type: :requests, klass: RP::Models::Record)
8
8
  db = @datasource.db
9
9
 
10
10
  @throughput_report = RP::Reports::ThroughputReport.new(db)
@@ -15,7 +15,7 @@ class RailsPerformanceController < BaseController
15
15
  end
16
16
 
17
17
  def summary
18
- @datasource = RP::DataSource.new(prepare_query)
18
+ @datasource = RP::DataSource.new(**prepare_query, type: :requests, klass: RP::Models::Record)
19
19
  db = @datasource.db
20
20
 
21
21
  @throughput_report = RP::Reports::ThroughputReport.new(db)
@@ -29,7 +29,7 @@ class RailsPerformanceController < BaseController
29
29
 
30
30
  respond_to do |format|
31
31
  format.js {}
32
- format.any { render plain: "Doesn't open in new window" }
32
+ format.any { render plain: "Doesn't open in new window. Wait until full page load." }
33
33
  end
34
34
  end
35
35
 
@@ -39,31 +39,45 @@ class RailsPerformanceController < BaseController
39
39
  @data = @report.data
40
40
  respond_to do |format|
41
41
  format.js {}
42
- format.any { render plain: "Doesn't open in new window" }
42
+ format.any { render plain: "Doesn't open in new window. Wait until full page load." }
43
43
  end
44
44
  end
45
45
 
46
46
  def crashes
47
- @datasource = RP::DataSource.new(prepare_query({status_eq: 500}))
47
+ @datasource = RP::DataSource.new(**prepare_query({status_eq: 500}), type: :requests, klass: RP::Models::Record)
48
48
  db = @datasource.db
49
49
  @report = RP::Reports::CrashReport.new(db)
50
50
  @data = @report.data
51
51
  end
52
52
 
53
53
  def requests
54
- @datasource = RP::DataSource.new(prepare_query)
54
+ @datasource = RP::DataSource.new(**prepare_query, type: :requests, klass: RP::Models::Record)
55
55
  db = @datasource.db
56
56
  @report = RP::Reports::RequestsReport.new(db, group: :controller_action_format, sort: :count)
57
57
  @data = @report.data
58
58
  end
59
59
 
60
60
  def recent
61
- @datasource = RP::DataSource.new(prepare_query)
61
+ @datasource = RP::DataSource.new(**prepare_query, type: :requests, klass: RP::Models::Record)
62
62
  db = @datasource.db
63
63
  @report = RP::Reports::RecentRequestsReport.new(db)
64
64
  @data = @report.data
65
65
  end
66
66
 
67
+ def jobs
68
+ @datasource = RP::DataSource.new(**prepare_query, type: :jobs, klass: RP::Models::JobRecord)
69
+ db = @datasource.db
70
+
71
+ @throughput_report = RP::Reports::ThroughputReport.new(db)
72
+ @throughput_report_data = @throughput_report.data
73
+
74
+ @response_time_report = RP::Reports::ResponseTimeReport.new(db)
75
+ @response_time_report_data = @response_time_report.data
76
+
77
+ @recent_report = RP::Reports::RecentRequestsReport.new(db)
78
+ @recent_report_data = @recent_report.data(:jobs)
79
+ end
80
+
67
81
  private
68
82
 
69
83
  def prepare_query(query = params)
@@ -9,7 +9,7 @@ module RailsPerformanceHelper
9
9
  def duraction_alert_class(duration_str)
10
10
  if duration_str.to_s =~ /(\d+.?\d+?)/
11
11
  duration = $1.to_f
12
- if duration >= 100
12
+ if duration >= 500
13
13
  'has-background-danger has-text-white-bis'
14
14
  elsif duration >= 200
15
15
  'has-background-warning has-text-black-ter'
@@ -107,6 +107,8 @@ module RailsPerformanceHelper
107
107
  "is-active" if controller_name == "rails_performance" && action_name == "requests"
108
108
  when :recent
109
109
  "is-active" if controller_name == "rails_performance" && action_name == "recent"
110
+ when :jobs
111
+ "is-active" if controller_name == "rails_performance" && action_name == "jobs"
110
112
  end
111
113
  end
112
114
  end
@@ -20,6 +20,4 @@
20
20
  </tr>
21
21
  <% end %>
22
22
  </tbody>
23
- </table>
24
-
25
- <br/>
23
+ </table>
@@ -25,7 +25,7 @@
25
25
  <% content_for :on_load do %>
26
26
  <script>
27
27
  var data1 = <%= raw @throughput_report_data.to_json %>;
28
- showTIRChart('throughput_report_chart', data1);
28
+ showTIRChart('throughput_report_chart', data1, ' rpm', 'RPM');
29
29
 
30
30
  var data2 = <%= raw @response_time_report_data.to_json %>;
31
31
  showRTChart('response_time_report_chart', data2);
@@ -1,4 +1,4 @@
1
- function showTIRChart(div, data) {
1
+ function showTIRChart(div, data, addon, name) {
2
2
  Highcharts.chart(div, {
3
3
  time: {
4
4
  timezone: 'Europe/Kiev'
@@ -24,7 +24,7 @@ function showTIRChart(div, data) {
24
24
  if (this.y == 0) {
25
25
  return '';
26
26
  }
27
- return this.y + ' rpm';
27
+ return this.y + addon;
28
28
  }
29
29
  },
30
30
  xAxis: {
@@ -69,7 +69,7 @@ function showTIRChart(div, data) {
69
69
  },
70
70
  series: [{
71
71
  type: 'area',
72
- name: 'RPM',
72
+ name: name,
73
73
  data: data,
74
74
  fillOpacity: 0.3,
75
75
  lineWidth: 1,
@@ -0,0 +1,73 @@
1
+ <title>Number of Requests to the Application</title>
2
+
3
+ <% unless @datasource.default? %>
4
+ <%#= link_to raw("&larr; Back"), rails_performance_path, class: "back_link" %>
5
+ <% end %>
6
+
7
+ <div class="card">
8
+ <div class="card-content">
9
+ <h2 class="subtitle">Sidekiq Workers Throughput Report</h2>
10
+ <div id="throughput_report_chart" class="chart"></div>
11
+ <p class="content is-small">All workers in the application</p>
12
+ </div>
13
+ </div>
14
+
15
+ <br/>
16
+
17
+ <div class="card">
18
+ <div class="card-content">
19
+ <h2 class="subtitle">Average Execution Time</h2>
20
+ <div id="response_time_report_chart" class="chart"></div>
21
+ <p class="content is-small">All workers in the application</p>
22
+ </div>
23
+ </div>
24
+
25
+ <div class="card">
26
+ <div class="card-content">
27
+ <h2 class="subtitle">Recent Jobs (last <%= RailsPerformance::Reports::RecentRequestsReport::TIME_WINDOW / 60 %> minutes)<h2>
28
+
29
+ <table class="table is-fullwidth is-hoverable is-narrow">
30
+ <thead>
31
+ <tr>
32
+ <th data-sort="string">Datetime</th>
33
+ <th data-sort="string">Queue</th>
34
+ <th data-sort="string">Worker</th>
35
+ <th data-sort="string">Job ID</th>
36
+ <th data-sort="string">Status</th>
37
+ <th data-sort="float">Duration</th>
38
+ <th>Message</th>
39
+ <th></th>
40
+ </tr>
41
+ </thead>
42
+ <tbody>
43
+ <% if @recent_report_data.empty? %>
44
+ <tr>
45
+ <td colspan="10">Nothing to show here. Try to make a few requests in main app.</td>
46
+ </tr>
47
+ <% end %>
48
+ <% @recent_report_data.each do |e| %>
49
+ <tr>
50
+ <td><%= format_datetime e[:datetime] %></td>
51
+ <td><%= e[:queue] %></td>
52
+ <td><%= e[:worker] %></td>
53
+ <td><%= e[:jid] %></td>
54
+ <td><%= status_tag e[:status] %></td>
55
+ <td class="nowrap"><%= ms e[:duration] %></td>
56
+ <td><%= e[:message] %></td>
57
+ </tr>
58
+ <% end %>
59
+ </tbody>
60
+ </table>
61
+ </div>
62
+ </div>
63
+
64
+
65
+ <% content_for :on_load do %>
66
+ <script>
67
+ var data1 = <%= raw @throughput_report_data.to_json %>;
68
+ showTIRChart('throughput_report_chart', data1, ' jobs / minute', 'Jobs');
69
+
70
+ var data2 = <%= raw @response_time_report_data.to_json %>;
71
+ showRTChart('response_time_report_chart', data2);
72
+ </script>
73
+ <% end %>
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>Rails Performance</title>
7
7
  <%= csrf_meta_tags %>
8
- <%= csp_meta_tag %>
8
+ <%= csp_meta_tag if ::Rails::VERSION::MAJOR.to_i >= 5 %>
9
9
  <%= render '/rails_performance/stylesheets/stylesheets' %>
10
10
  <link rel="shortcut icon" href="/favicon.ico">
11
11
  </head>
@@ -1,11 +1,11 @@
1
1
  <div class="card">
2
2
  <div class="card-content">
3
-
4
3
  <h2 class="subtitle">Recent Requests (last <%= RailsPerformance::Reports::RecentRequestsReport::TIME_WINDOW / 60 %> minutes)<h2>
5
4
 
6
5
  <table class="table is-fullwidth is-hoverable is-narrow">
7
6
  <thead>
8
7
  <tr>
8
+ <th data-sort="string"></th>
9
9
  <th data-sort="string">Datetime</th>
10
10
  <th data-sort="string">Controller#action</th>
11
11
  <th data-sort="string">Method</th>
@@ -26,6 +26,13 @@
26
26
  <% end %>
27
27
  <% @data.each do |e| %>
28
28
  <tr>
29
+ <td>
30
+ <% if e[:request_id].present? %>
31
+ <%= link_to rails_performance.rails_performance_trace_path(id: e[:request_id]), remote: true do %>
32
+ <span class="stats_icon_max"><%= icon 'activity' %></span>
33
+ <% end %>
34
+ <% end %>
35
+ </td>
29
36
  <td><%= format_datetime e[:datetime] %></td>
30
37
  <td><%= link_to e[:controller] + '#' + e[:action], rails_performance.rails_performance_summary_path({controller_eq: e[:controller], action_eq: e[:action]}), remote: true %></td>
31
38
  <td><%= e[:method] %></td>
@@ -18,6 +18,9 @@
18
18
  <%= link_to 'Requests Analysis', rails_performance.rails_performance_requests_url, class: "navbar-item #{active?(:requests)}" %>
19
19
  <%= link_to '500 Errors', rails_performance.rails_performance_crashes_url, class: "navbar-item #{active?(:crashes)}" %>
20
20
  <%= link_to 'Recent Requests', rails_performance.rails_performance_recent_url, class: "navbar-item #{active?(:recent)}" %>
21
+ <% if defined?(Sidekiq) %>
22
+ <%= link_to 'Sidekiq', rails_performance.rails_performance_jobs_url, class: "navbar-item #{active?(:jobs)}" %>
23
+ <% end %>
21
24
  </div>
22
25
 
23
26
  <div class="navbar-end">
@@ -2,7 +2,7 @@ window.panel.header.html(window.panel.close + '<%= j report_name(@datasource.q)
2
2
  window.panel.content.html("<%= j render '/rails_performance/summary' %>");
3
3
 
4
4
  var data1 = <%= raw @throughput_report_data.to_json %>;
5
- showTIRChart('throughput_report_chart_mini', data1);
5
+ showTIRChart('throughput_report_chart_mini', data1, ' rpm', 'RPM');
6
6
 
7
7
  var data2 = <%= raw @response_time_report_data.to_json %>;
8
8
  showRTChart('response_time_report_chart_mini', data2);
@@ -7,6 +7,8 @@ RailsPerformance::Engine.routes.draw do
7
7
 
8
8
  get '/trace/:id' => 'rails_performance#trace', as: :rails_performance_trace
9
9
  get '/summary' => 'rails_performance#summary', as: :rails_performance_summary
10
+
11
+ get '/jobs' => 'rails_performance#jobs', as: :rails_performance_jobs
10
12
  end
11
13
 
12
14
  Rails.application.routes.draw do
@@ -4,7 +4,9 @@ require_relative "./rails_performance/version.rb"
4
4
  require_relative "rails_performance/rails/query_builder.rb"
5
5
  require_relative "rails_performance/rails/middleware.rb"
6
6
  require_relative "rails_performance/data_source.rb"
7
+ require_relative "rails_performance/models/base_record.rb"
7
8
  require_relative "rails_performance/models/record.rb"
9
+ require_relative "rails_performance/models/job_record.rb"
8
10
  require_relative "rails_performance/utils.rb"
9
11
  require_relative "rails_performance/reports/base_report.rb"
10
12
  require_relative "rails_performance/reports/requests_report.rb"
@@ -1,8 +1,10 @@
1
1
  module RailsPerformance
2
2
  class DataSource
3
- attr_reader :q
3
+ attr_reader :q, :klass, :type
4
4
 
5
- def initialize(q: {})
5
+ def initialize(q: {}, type:, klass:)
6
+ @klass = klass
7
+ @type = type
6
8
  q[:on] ||= Date.today
7
9
  @q = q
8
10
 
@@ -12,7 +14,7 @@ module RailsPerformance
12
14
  def db
13
15
  result = RP::Models::Collection.new
14
16
  (RP::Utils.days + 1).times do |e|
15
- RP::DataSource.new(q: self.q.merge({ on: e.days.ago.to_date })).add_to(result)
17
+ RP::DataSource.new(q: self.q.merge({ on: e.days.ago.to_date }), klass: klass, type: type).add_to(result)
16
18
  end
17
19
  result
18
20
  end
@@ -34,41 +36,46 @@ module RailsPerformance
34
36
  return [] if keys.blank?
35
37
 
36
38
  keys.each_with_index do |key, index|
37
- yield RP::Models::Record.new(key, values[index])
39
+ yield klass.new(key, values[index])
38
40
  end
39
41
  end
40
42
 
41
43
  private
42
44
 
43
- # key = performance|
44
- # controller|HomeController|
45
- # action|index|
46
- # format|html|
47
- # status|200|
48
- # datetime|20200124T0523|
49
- # datetimei|1579861423|
50
- # method|GET|
51
- # path|/|
52
- # END
53
-
54
- def compile_query
45
+ def query
46
+ case type
47
+ when :requests
48
+ "performance|*#{compile_requests_query}*|END"
49
+ when :jobs
50
+ "jobs|*#{compile_jobs_query}*|END"
51
+ else
52
+ raise "wrong type for datasource query builder"
53
+ end
54
+ end
55
+
56
+ def compile_requests_query
55
57
  str = []
56
58
 
57
59
  str << "controller|#{q[:controller]}|" if q[:controller].present?
58
60
  str << "action|#{q[:action]}|" if q[:action].present?
59
61
  str << "format|#{q[:format]}|" if q[:format].present?
60
62
  str << "status|#{q[:status]}|" if q[:status].present?
61
-
62
63
  str << "datetime|#{q[:on].strftime('%Y%m%d')}*|" if q[:on].present?
63
-
64
64
  str << "method|#{q[:method]}|" if q[:method].present?
65
65
  str << "path|#{q[:path]}|" if q[:path].present?
66
66
 
67
67
  str.join("*")
68
68
  end
69
69
 
70
- def query
71
- "performance|*#{compile_query}*|END"
70
+ def compile_jobs_query
71
+ str = []
72
+
73
+ str << "queue|#{q[:queue]}|" if q[:queue].present?
74
+ str << "worker|#{q[:worker]}|" if q[:worker].present?
75
+ str << "datetime|#{q[:on].strftime('%Y%m%d')}*|" if q[:on].present?
76
+ str << "status|#{q[:status]}|" if q[:status].present?
77
+
78
+ str.join("*")
72
79
  end
73
80
 
74
81
  end
@@ -7,7 +7,11 @@ module RailsPerformance
7
7
 
8
8
  if RailsPerformance.try(:enabled) # for rails c
9
9
 
10
- config.app_middleware.insert_after ActionDispatch::Executor, RailsPerformance::Rails::Middleware
10
+ if ::Rails::VERSION::MAJOR.to_i >= 5
11
+ config.app_middleware.insert_after ActionDispatch::Executor, RailsPerformance::Rails::Middleware
12
+ else
13
+ config.app_middleware.insert_after ActionDispatch::Static, RailsPerformance::Rails::Middleware
14
+ end
11
15
 
12
16
  initializer :configure_metrics, after: :initialize_logger do
13
17
  ActiveSupport::Notifications.subscribe(
@@ -19,7 +23,15 @@ module RailsPerformance
19
23
  ActionView::LogSubscriber.send :prepend, RailsPerformance::Extensions::View
20
24
  ActiveRecord::LogSubscriber.send :prepend, RailsPerformance::Extensions::Db
21
25
  end
26
+ end
22
27
 
28
+ if const_defined?("Sidekiq")
29
+ require_relative './gems/sidekiq.rb'
30
+ Sidekiq.configure_server do |config|
31
+ config.server_middleware do |chain|
32
+ chain.add RailsPerformance::Gems::Sidekiq
33
+ end
34
+ end
23
35
  end
24
36
 
25
37
  end
@@ -0,0 +1,35 @@
1
+ module RailsPerformance
2
+ module Gems
3
+ class Sidekiq
4
+
5
+ def initialize(options=nil)
6
+ end
7
+
8
+ def call(worker, msg, queue)
9
+ now = Time.now
10
+ data = {
11
+ enqueued_ati: msg['enqueued_at'].to_i,
12
+ created_ati: msg['created_at'].to_i,
13
+ jid: msg['jid'],
14
+ queue: queue,
15
+ start_timei: now.to_i,
16
+ datetime: now.strftime(RailsPerformance::FORMAT),
17
+ worker: msg['wrapped'.freeze] || worker.class.to_s
18
+ }
19
+ begin
20
+ yield
21
+ data[:status] = "success"
22
+ rescue Exception => ex
23
+ data[:status] = "exception"
24
+ data[:message] = ex.message
25
+ ensure
26
+ # store in ms instead of seconds
27
+ data[:duration] = (Time.now - now) * 1000
28
+ #puts data
29
+ RailsPerformance::Utils.log_job_in_redis(data)
30
+ end
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,24 @@
1
+ module RailsPerformance
2
+ module Models
3
+ class BaseRecord
4
+ def value
5
+ @value ||= JSON.parse(@json || "{}")
6
+ end
7
+
8
+ def duration
9
+ value['duration']
10
+ end
11
+
12
+ private
13
+
14
+ def ms(e)
15
+ if e
16
+ e.to_f.round(1).to_s + " ms"
17
+ else
18
+ nil
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,48 @@
1
+ module RailsPerformance
2
+ module Models
3
+ class JobRecord < BaseRecord
4
+ attr_reader :queue, :worker, :jid, :created_ati, :enqueued_ati, :datetime, :start_timei, :status
5
+
6
+ # key = job-performance
7
+ # |queue|default
8
+ # |worker|SimpleWorker
9
+ # |jid|7d48fbf20976c224510dbc60
10
+ # |datetime|20200124T0523
11
+ # |created_ati|1583146613
12
+ # |enqueued_ati|1583146613
13
+ # |start_timei|1583146614
14
+ # |status|success|END
15
+ # value = JSON
16
+ def initialize(key, value)
17
+ @json = value
18
+
19
+ items = key.split("|")
20
+
21
+ @queue = items[2]
22
+ @worker = items[4]
23
+ @jid = items[6]
24
+ @datetime = items[8]
25
+ @created_ati = items[10]
26
+ @enqueued_ati = items[12]
27
+ @start_timei = items[14]
28
+ @status = items[16]
29
+ end
30
+
31
+ def to_h
32
+ {
33
+ queue: queue,
34
+ worker: worker,
35
+ jid: jid,
36
+ datetime: datetime,
37
+ created_ati: created_ati,
38
+ enqueued_ati: enqueued_ati,
39
+ start_timei: start_timei,
40
+ duration: duration,
41
+ status: status,
42
+ message: value['message']
43
+ }
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -1,9 +1,8 @@
1
1
  module RailsPerformance
2
2
  module Models
3
- class Record
3
+ class Record < BaseRecord
4
4
  attr_reader :controller, :action, :format, :status, :datetime, :datetimei, :method, :path, :request_id
5
5
 
6
-
7
6
  def Record.find_by(request_id:)
8
7
  keys, values = RP::Utils.fetch_from_redis("performance|*|request_id|#{request_id}|*")
9
8
 
@@ -42,10 +41,6 @@ module RailsPerformance
42
41
  @request_id = items[18]
43
42
  end
44
43
 
45
- def value
46
- @value ||= JSON.parse(@json || "{}")
47
- end
48
-
49
44
  def controller_action
50
45
  "#{controller}##{action}"
51
46
  end
@@ -61,23 +56,13 @@ module RailsPerformance
61
56
  format: format,
62
57
  method: method,
63
58
  path: path,
64
- duration: ms(value['duration']),
59
+ duration: ms(duration),
65
60
  view_runtime: ms(value['view_runtime']),
66
61
  db_runtime: ms(value['db_runtime']),
67
62
  HTTP_REFERER: value['HTTP_REFERER']
68
63
  }
69
64
  end
70
65
 
71
- private
72
-
73
- def ms(e)
74
- if e
75
- e.to_f.round(1).to_s + " ms"
76
- else
77
- nil
78
- end
79
- end
80
-
81
66
  end
82
67
  end
83
68
  end
@@ -6,6 +6,10 @@ module RailsPerformance
6
6
  end
7
7
 
8
8
  def call(env)
9
+ dup.call!(env)
10
+ end
11
+
12
+ def call!(env)
9
13
  @status, @headers, @response = @app.call(env)
10
14
 
11
15
  #t = Time.now
@@ -7,25 +7,48 @@ module RailsPerformance
7
7
  @sort ||= :datetime
8
8
  end
9
9
 
10
- def data
10
+ def data(type = :requests) # most popular type
11
11
  db.data.collect do |record|
12
- {
13
- controller: record.controller,
14
- action: record.action,
15
- format: record.format,
16
- status: record.status,
17
- method: record.method,
18
- path: record.path,
19
- request_id: record.request_id,
20
- datetime: Time.at(record.datetimei.to_i),
21
- duration: record.value['duration'],
22
- db_runtime: record.value['db_runtime'],
23
- view_runtime: record.value['view_runtime'],
24
- }
12
+ case type
13
+ when :requests
14
+ record_hash(record)
15
+ when :jobs
16
+ job_hash(record)
17
+ end
25
18
  end
26
19
  .select{|e| e if e[:datetime] >= TIME_WINDOW.ago}
27
20
  .sort{|a, b| b[sort] <=> a[sort]}
28
21
  end
22
+
23
+ private
24
+
25
+ def record_hash(record)
26
+ {
27
+ controller: record.controller,
28
+ action: record.action,
29
+ format: record.format,
30
+ status: record.status,
31
+ method: record.method,
32
+ path: record.path,
33
+ request_id: record.request_id,
34
+ datetime: Time.at(record.datetimei.to_i),
35
+ duration: record.value['duration'],
36
+ db_runtime: record.value['db_runtime'],
37
+ view_runtime: record.value['view_runtime'],
38
+ }
39
+ end
40
+
41
+ def job_hash(record)
42
+ {
43
+ worker: record.worker,
44
+ queue: record.queue,
45
+
46
+ jid: record.jid,
47
+ status: record.status,
48
+ datetime: Time.at(record.start_timei.to_i),
49
+ duration: record.value['duration'],
50
+ }
51
+ end
29
52
  end
30
53
 
31
54
 
@@ -12,28 +12,21 @@ module RailsPerformance
12
12
  now.strftime("%H:%M")
13
13
  end
14
14
 
15
+ def Utils.log_job_in_redis(e)
16
+ key = "jobs|queue|#{e[:queue]}|worker|#{e[:worker]}|jid|#{e[:jid]}|datetime|#{e[:datetime]}|created_ati|#{e[:created_ati]}|enqueued_ati|#{e[:enqueued_ati]}|start_timei|#{e[:start_timei]}|status|#{e[:status]}|END"
17
+ value = { message: e[:message], duration: e[:duration] }
18
+ Utils.save_to_redis(key, value)
19
+ end
20
+
15
21
  def Utils.log_request_in_redis(e)
16
22
  value = e.slice(:view_runtime, :db_runtime, :duration, :HTTP_REFERER)
17
23
  key = "performance|controller|#{e[:controller]}|action|#{e[:action]}|format|#{e[:format]}|status|#{e[:status]}|datetime|#{e[:datetime]}|datetimei|#{e[:datetimei]}|method|#{e[:method]}|path|#{e[:path]}|request_id|#{e[:request_id]}|END"
18
-
19
- # puts " [SAVE] key ---> #{key}\n"
20
- # puts " value ---> #{value.to_json}\n\n"
21
-
22
- RP.redis.set(key, value.to_json)
23
- RP.redis.expire(key, RP.duration.to_i)
24
-
25
- true
24
+ Utils.save_to_redis(key, value)
26
25
  end
27
26
 
28
27
  def Utils.log_trace_in_redis(request_id, value)
29
28
  key = "trace|#{request_id}"
30
-
31
- # puts " [SAVE] key ---> #{key}\n"
32
- # puts " value ---> #{value.to_json}\n\n"
33
- # pp value
34
-
35
- RP.redis.set(key, value.to_json)
36
- RP.redis.expire(key, RailsPerformance::Reports::RecentRequestsReport::TIME_WINDOW.to_i)
29
+ Utils.save_to_redis(key, value, RailsPerformance::Reports::RecentRequestsReport::TIME_WINDOW.to_i)
37
30
  end
38
31
 
39
32
  def Utils.fetch_from_redis(query)
@@ -47,7 +40,7 @@ module RailsPerformance
47
40
  end
48
41
 
49
42
  def Utils.days
50
- (RP.duration % 24.days).parts[:days] + 1
43
+ (RP.duration / 1.day) + 1
51
44
  end
52
45
 
53
46
  def Utils.median(array)
@@ -64,5 +57,12 @@ module RailsPerformance
64
57
  end
65
58
  end
66
59
 
60
+ def Utils.save_to_redis(key, value, expire = RP.duration.to_i)
61
+ # puts " [SAVE] key ---> #{key}\n"
62
+ # puts " value ---> #{value.to_json}\n\n"
63
+ RP.redis.set(key, value.to_json)
64
+ RP.redis.expire(key, expire.to_i)
65
+ end
66
+
67
67
  end
68
- end
68
+ end
@@ -1,3 +1,3 @@
1
1
  module RailsPerformance
2
- VERSION = '0.9.0'
2
+ VERSION = '0.9.4'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_performance
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.9.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Igor Kasyanchuk
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-11 00:00:00.000000000 Z
11
+ date: 2020-08-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -80,7 +80,22 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
- description: Track number of requests to your app
83
+ - !ruby/object:Gem::Dependency
84
+ name: sidekiq
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: 3rd party dependency-free solution how to monitor performance of your
98
+ Rails applications.
84
99
  email:
85
100
  - igorkasyanchuk@gmail.com
86
101
  executables: []
@@ -115,6 +130,7 @@ files:
115
130
  - app/views/rails_performance/javascripts/rails.js
116
131
  - app/views/rails_performance/javascripts/stupidtable.min.js
117
132
  - app/views/rails_performance/javascripts/table.js
133
+ - app/views/rails_performance/jobs.html.erb
118
134
  - app/views/rails_performance/layouts/rails_performance.html.erb
119
135
  - app/views/rails_performance/recent.html.erb
120
136
  - app/views/rails_performance/requests.html.erb
@@ -130,9 +146,12 @@ files:
130
146
  - lib/rails_performance/data_source.rb
131
147
  - lib/rails_performance/engine.rb
132
148
  - lib/rails_performance/extensions/capture_everything.rb
149
+ - lib/rails_performance/gems/sidekiq.rb
133
150
  - lib/rails_performance/instrument/metrics_collector.rb
151
+ - lib/rails_performance/models/base_record.rb
134
152
  - lib/rails_performance/models/collection.rb
135
153
  - lib/rails_performance/models/current_request.rb
154
+ - lib/rails_performance/models/job_record.rb
136
155
  - lib/rails_performance/models/record.rb
137
156
  - lib/rails_performance/rails/middleware.rb
138
157
  - lib/rails_performance/rails/query_builder.rb
@@ -165,8 +184,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
165
184
  - !ruby/object:Gem::Version
166
185
  version: '0'
167
186
  requirements: []
168
- rubygems_version: 3.0.6
187
+ rubygems_version: 3.0.3
169
188
  signing_key:
170
189
  specification_version: 4
171
- summary: Track number of requests to your app
190
+ summary: Simple Rails Performance tracker. Alternative to the NewRelic, Datadog or
191
+ other services.
172
192
  test_files: []