rails_performance 0.0.1.18 → 0.9.2

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: 39436989afe127c5268d13685c11384a13051b44efdc4dac1705e671c44a0147
4
- data.tar.gz: 72988c7f770d87482c82d4699c3a2956acc4268addb22820a4093de20d1de9e8
3
+ metadata.gz: 016c0a4dada04d91fb392c7be96962f28bd65d7a55ca436379eff445ac6f2c76
4
+ data.tar.gz: c14155acbc63d8c043f60ddf719e33da0f9dc1d6809fb279c34dc4ba39ea6afe
5
5
  SHA512:
6
- metadata.gz: 9ed81e6a440c901d68b78bda24c00340a905ac18559499f5e58f341f82961b1e3c2cfda239fbd3c286fcf4b94a61675972db1fb64e696e805df99cc8b56e2577
7
- data.tar.gz: faf3d55fb96cd5afa23c39ecc4dc0bcb76ec0caa84e9520229ead2660f8b7557bc4497b8ba5aee8c0e1342de5b3cd38ac272db70616f8ca041da1c61e0567bbc
6
+ metadata.gz: 14be1cffee56e247f968e1001788c2c716d3d4e622b2c584bb8cfb60399bcca20f4665324cd7b15f4bcaf3102af331cba0b20950b67de005b48cb1faa3533374
7
+ data.tar.gz: e982081222f459103fc3d801f2fb18add3f2e93b2efc1073c33ed6501138ca5b536cce7ba6e763849724ab3673e0c3ca3f2a9aa4583665d6c0397b4652c04aad
data/README.md CHANGED
@@ -1,26 +1,44 @@
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
 
9
- - throughput report (see amount of requests per minute)
13
+ - throughput report (see amount of RPM (requests per minute))
10
14
  - an average response time
11
15
  - the slowest controllers & actions
12
- - duration of total time spent per request, views rendering, DB execution
13
- - simple crash reports
16
+ - total duration of time spent per request, views rendering, DB
17
+ - SQL queries, rendering logs in "Recent Requests" section
18
+ - simple 500-crashes reports
19
+ - track Sidekiq jobs performance
14
20
 
15
- All data is stored in local Redis and not sent to 3rd party servers.
21
+ All data are stored in `local` Redis and not sent to any 3rd party servers.
16
22
 
17
23
  ## Production
18
24
 
19
- Gem is production-ready. At least on my applications with ~800 unique users per day it works well.
25
+ Gem is production-ready. At least on my 2 applications with ~800 unique users per day it works perfectly.
26
+
27
+ Just don't forget to protect performance dashboard with http basic auth or check of current_user.
20
28
 
21
29
  ## Usage
22
30
 
23
- Create `config/initializers/rails_performance.rb`
31
+ ```
32
+ 1. Add gem to the Gemfile (in appropriate group if needed)
33
+ 2. Start rails server
34
+ 3. Make a few requests to your app
35
+ 4. open localhost:3000/rails/performance
36
+ 5. Tune the configuration and deploy to production
37
+ ```
38
+
39
+ Default configulation is listed below. But you can overide it.
40
+
41
+ Create `config/initializers/rails_performance.rb` in your app:
24
42
 
25
43
  ```ruby
26
44
  RailsPerformance.setup do |config|
@@ -47,6 +65,12 @@ Add this line to your application's Gemfile:
47
65
 
48
66
  ```ruby
49
67
  gem 'rails_performance'
68
+
69
+ # or
70
+
71
+ group :development, :production do
72
+ gem 'rails_performance'
73
+ end
50
74
  ```
51
75
 
52
76
  And then execute:
@@ -58,16 +82,21 @@ You must also have installed Redis server, because this gem is storing data into
58
82
 
59
83
  After installation and configuration, start your Rails application, make a few requests, and open `https://localhost:3000/rails/performance` URL.
60
84
 
85
+ ## How it works
86
+
87
+ ![Schema](docs/rails_performance.png)
88
+
61
89
  ## Limitations
62
90
 
63
91
  - it doesn't track params of POST/PUT requests
64
- - it doesn't track SQL queries (at least for now)
65
92
  - it doesn't track Redis/ElasticSearch or other apps
66
93
  - it can't compare historical data
67
94
  - 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
68
95
 
69
96
  ## Redis
70
97
 
98
+ Gem is using Redis. This is the only one dependency.
99
+
71
100
  All information is stored into Redis. The default expiration time is set to `config.duration` from the configuration.
72
101
 
73
102
  ## Development & Testing
@@ -77,12 +106,14 @@ Just clone the repo, setup dummy app (`rails db:migrate`).
77
106
  After this:
78
107
 
79
108
  - rails s
80
- - rake tests
109
+ - rake test
81
110
 
82
111
  Like a regular web development.
83
112
 
84
113
  Please note that to simplify integration with other apps all CSS/JS are bundled inside, and delivered in body of the request. This is to avoid integration with assets pipeline or webpacker.
85
114
 
115
+ For UI changes you need to use Bulma CSS (https://bulma.io/documentation).
116
+
86
117
  ## Why
87
118
 
88
119
  The idea of this gem grew from curriosity how many RPM my app receiving per day. Later it evolutionated to something more powerful.
@@ -90,32 +121,33 @@ The idea of this gem grew from curriosity how many RPM my app receiving per day.
90
121
  ## TODO
91
122
 
92
123
  - documentation in Readme
124
+ - capture stacktrace of 500 errors and show in side panel
93
125
  - generator for initial config
94
- - gif with demo
95
- - time/zone config?
96
126
  - CI for tests
97
- - connected charts
98
- - ability to zoom to see requests withing specific datime range
99
- - better hint
127
+ - time/zone config?
128
+ - connected charts on dashboard, when zoom, when hover?
129
+ - ability to zoom to see requests withing specific datetime range
130
+ - better hints?
100
131
  - export to csv
101
- - http basic auth config
102
- - current_user auth config
103
132
  - better stats tooltip, do not show if nothing to show
104
- - dark mode toggle? save to the cookies
105
- - integration with elastic search
133
+ - dark mode toggle? save to the cookies?
134
+ - integration with elastic search? or other?
106
135
  - monitor active job (sidekiq)?
107
- - logo?
136
+ - better logo?
108
137
  - number of requests last 24 hours, hour, etc.
109
- - integration with logger, to see the details of request?
110
- - no results if empty table (on crash page)
111
- - capture referal for 404 page?
112
- - SQL
113
- - Rendering
114
- - scroll to the top details
138
+ - collect deprecation.rails
139
+ - fix misspellings?
140
+ - show "loading banner" until jquery is loaded?
141
+ - better UI on smaller screens? Recent requests when URL's are long? Truncate with CSS?
142
+ - rules for highlighting durations? how many ms to show warning, alert
115
143
 
116
144
  ## Contributing
117
145
 
118
- You are welcome to contribute.
146
+ You are welcome to contribute. I've a big list of TODO.
147
+
148
+ ## Big thanks to contributors
149
+
150
+ - https://github.com/synth
119
151
 
120
152
  ## License
121
153
 
@@ -1,7 +1,6 @@
1
1
  class BaseController < ActionController::Base
2
2
  layout 'rails_performance/layouts/rails_performance'
3
3
 
4
- before_action :leave_finger_print
5
4
  before_action :verify_access
6
5
 
7
6
  if RailsPerformance.http_basic_authentication_enabled
@@ -12,10 +11,6 @@ class BaseController < ActionController::Base
12
11
 
13
12
  private
14
13
 
15
- def leave_finger_print
16
- Thread.current[:in_rails_performance] = true
17
- end
18
-
19
14
  def verify_access
20
15
  result = RailsPerformance.verify_access_proc.call(self)
21
16
  redirect_to('/', error: 'Access Denied', status: 401) unless result
@@ -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,40 +29,55 @@ 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
 
36
36
  def trace
37
+ @record = RP::Models::Record.find_by(request_id: params[:id])
37
38
  @report = RP::Reports::TraceReport.new(request_id: params[:id])
38
39
  @data = @report.data
39
40
  respond_to do |format|
40
41
  format.js {}
41
- 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." }
42
43
  end
43
44
  end
44
45
 
45
46
  def crashes
46
- @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)
47
48
  db = @datasource.db
48
49
  @report = RP::Reports::CrashReport.new(db)
49
50
  @data = @report.data
50
51
  end
51
52
 
52
53
  def requests
53
- @datasource = RP::DataSource.new(prepare_query)
54
+ @datasource = RP::DataSource.new(**prepare_query, type: :requests, klass: RP::Models::Record)
54
55
  db = @datasource.db
55
56
  @report = RP::Reports::RequestsReport.new(db, group: :controller_action_format, sort: :count)
56
57
  @data = @report.data
57
58
  end
58
59
 
59
60
  def recent
60
- @datasource = RP::DataSource.new(prepare_query)
61
+ @datasource = RP::DataSource.new(**prepare_query, type: :requests, klass: RP::Models::Record)
61
62
  db = @datasource.db
62
63
  @report = RP::Reports::RecentRequestsReport.new(db)
63
64
  @data = @report.data
64
65
  end
65
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
+
66
81
  private
67
82
 
68
83
  def prepare_query(query = params)
@@ -6,6 +6,29 @@ module RailsPerformanceHelper
6
6
  value.nan? ? nil : value.round(1)
7
7
  end
8
8
 
9
+ def duraction_alert_class(duration_str)
10
+ if duration_str.to_s =~ /(\d+.?\d+?)/
11
+ duration = $1.to_f
12
+ if duration >= 500
13
+ 'has-background-danger has-text-white-bis'
14
+ elsif duration >= 200
15
+ 'has-background-warning has-text-black-ter'
16
+ else
17
+ 'has-background-success has-text-white-bis'
18
+ end
19
+ else
20
+ 'has-background-light'
21
+ end
22
+ end
23
+
24
+ def extract_duration(str)
25
+ if (str =~ /Duration: (\d+.?\d+?ms)/i)
26
+ $1
27
+ else
28
+ '-'
29
+ end
30
+ end
31
+
9
32
  def ms(value)
10
33
  result = round_it(value)
11
34
  result && result != 0 ? "#{result} ms" : '-'
@@ -27,14 +50,16 @@ module RailsPerformanceHelper
27
50
 
28
51
  def report_name(h)
29
52
  h.except(:on).collect do |k, v|
53
+ next if v.blank?
54
+
30
55
  %Q{
31
56
  <div class="control">
32
57
  <span class="tags has-addons">
33
58
  <span class="tag">#{k}</span>
34
- <span class="tag is-success">#{v}</span>
59
+ <span class="tag is-info is-light">#{v}</span>
35
60
  </span>
36
61
  </div>}
37
- end.join.html_safe
62
+ end.compact.join.html_safe
38
63
  end
39
64
 
40
65
  def status_tag(status)
@@ -82,6 +107,8 @@ module RailsPerformanceHelper
82
107
  "is-active" if controller_name == "rails_performance" && action_name == "requests"
83
108
  when :recent
84
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"
85
112
  end
86
113
  end
87
114
  end
@@ -2,16 +2,17 @@
2
2
  <tbody>
3
3
  <% @data.each do |e| %>
4
4
  <tr>
5
- <td><%= e["group"] %></td>
6
5
  <% if e["group"] == 'db' %>
7
- <td class="nowrap">
6
+ <td class="nowrap has-text-right <%= duraction_alert_class(e['duration']) %>">
8
7
  <%= e["duration"] %> ms
9
8
  </td>
10
9
  <td>
11
10
  <%= e["sql"] %>
12
11
  </td>
13
12
  <% elsif e["group"] == 'view' %>
14
- <td>-</td>
13
+ <td class="nowrap has-text-right <%= duraction_alert_class(extract_duration(e['message'])) %>">
14
+ <%= extract_duration(e["message"]) %>
15
+ </td>
15
16
  <td>
16
17
  <%= e["message"] %>
17
18
  </td>
@@ -16,6 +16,12 @@
16
16
  </tr>
17
17
  </thead>
18
18
  <tbody>
19
+ <% if @data.empty? %>
20
+ <tr>
21
+ <td colspan="9">We are glad that this list is empty ;)</td>
22
+ </tr>
23
+ <% end %>
24
+
19
25
  <% @data.each do |e| %>
20
26
  <tr>
21
27
  <td><%= format_datetime e[:datetime] %></td>
@@ -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 %>
@@ -1,7 +1,6 @@
1
1
  <div class="card">
2
2
  <div class="card-content">
3
-
4
- <h2 class="subtitle">Recent Requests (last <%= RailsPerformance::Reports::RecentRequestsReport::TIME_WINDOW %> minutes)<h2>
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>
@@ -19,6 +18,11 @@
19
18
  </tr>
20
19
  </thead>
21
20
  <tbody>
21
+ <% if @data.empty? %>
22
+ <tr>
23
+ <td colspan="10">Nothing to show here. Try to make a few requests in main app.</td>
24
+ </tr>
25
+ <% end %>
22
26
  <% @data.each do |e| %>
23
27
  <tr>
24
28
  <td><%= format_datetime e[:datetime] %></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);
@@ -1,4 +1,9 @@
1
- window.panel.header.html(window.panel.close);
1
+ <% if @record %>
2
+ window.panel.header.html(window.panel.close + "<%= j report_name(@record.to_h) %>");
3
+ <% else %>
4
+ window.panel.header.html(window.panel.close);
5
+ <% end %>
6
+
2
7
  window.panel.content.html("<%= j render '/rails_performance/trace' %>");
3
8
 
4
9
  showPanel();
@@ -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
@@ -5,8 +5,10 @@ require_relative './instrument/metrics_collector.rb'
5
5
  module RailsPerformance
6
6
  class Engine < ::Rails::Engine
7
7
 
8
- if RailsPerformance.enabled
8
+ if RailsPerformance.try(:enabled) # for rails c
9
+
9
10
  config.app_middleware.insert_after ActionDispatch::Executor, RailsPerformance::Rails::Middleware
11
+
10
12
  initializer :configure_metrics, after: :initialize_logger do
11
13
  ActiveSupport::Notifications.subscribe(
12
14
  "process_action.action_controller",
@@ -18,6 +20,16 @@ module RailsPerformance
18
20
  ActiveRecord::LogSubscriber.send :prepend, RailsPerformance::Extensions::Db
19
21
  end
20
22
  end
23
+
24
+ if const_defined?("Sidekiq")
25
+ require_relative './gems/sidekiq.rb'
26
+ Sidekiq.configure_server do |config|
27
+ config.server_middleware do |chain|
28
+ chain.add RailsPerformance::Gems::Sidekiq
29
+ end
30
+ end
31
+ end
32
+
21
33
  end
22
34
 
23
35
  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
@@ -18,6 +18,8 @@ module RailsPerformance
18
18
  def call(event_name, started, finished, event_id, payload)
19
19
  event = ActiveSupport::Notifications::Event.new(event_name, started, finished, event_id, payload)
20
20
 
21
+ return if event.payload[:path] =~ /^\/rails\/performance/
22
+
21
23
  record = {
22
24
  controller: event.payload[:controller],
23
25
  action: event.payload[:action],
@@ -32,7 +34,7 @@ module RailsPerformance
32
34
  duration: event.duration
33
35
  }
34
36
 
35
- Thread.current["RP_request_info"] = record
37
+ CurrentRequest.current.record = record
36
38
  end
37
39
  end
38
40
  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
@@ -1,6 +1,7 @@
1
1
  module RailsPerformance
2
2
  class CurrentRequest
3
3
  attr_reader :request_id, :storage
4
+ attr_accessor :record
4
5
 
5
6
  def CurrentRequest.init
6
7
  Thread.current[:rp_current_request] ||= CurrentRequest.new(SecureRandom.hex(16))
@@ -17,6 +18,7 @@ module RailsPerformance
17
18
  def initialize(request_id)
18
19
  @request_id = request_id
19
20
  @storage = []
21
+ @record = nil
20
22
  end
21
23
 
22
24
  def store(options = {})
@@ -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,8 +1,17 @@
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
+ def Record.find_by(request_id:)
7
+ keys, values = RP::Utils.fetch_from_redis("performance|*|request_id|#{request_id}|*")
8
+
9
+ return nil if keys.blank?
10
+ return nil if values.blank?
11
+
12
+ RP::Models::Record.new(keys[0], values[0])
13
+ end
14
+
6
15
  # key = performance|
7
16
  # controller|HomeController|
8
17
  # action|index|
@@ -32,10 +41,6 @@ module RailsPerformance
32
41
  @request_id = items[18]
33
42
  end
34
43
 
35
- def value
36
- @value ||= JSON.parse(@json || "{}")
37
- end
38
-
39
44
  def controller_action
40
45
  "#{controller}##{action}"
41
46
  end
@@ -43,6 +48,21 @@ module RailsPerformance
43
48
  def controller_action_format
44
49
  "#{controller}##{action}|#{format}"
45
50
  end
51
+
52
+ def to_h
53
+ {
54
+ controller: controller,
55
+ action: action,
56
+ format: format,
57
+ method: method,
58
+ path: path,
59
+ duration: ms(duration),
60
+ view_runtime: ms(value['view_runtime']),
61
+ db_runtime: ms(value['db_runtime']),
62
+ HTTP_REFERER: value['HTTP_REFERER']
63
+ }
64
+ end
65
+
46
66
  end
47
67
  end
48
68
  end
@@ -8,25 +8,28 @@ module RailsPerformance
8
8
  def call(env)
9
9
  @status, @headers, @response = @app.call(env)
10
10
 
11
- if !Thread.current[:in_rails_performance] && record = Thread.current["RP_request_info"]
12
- record[:status] ||= @status
13
- record[:request_id] = CurrentRequest.current.request_id
11
+ #t = Time.now
12
+ if record = CurrentRequest.current.record
13
+ begin
14
+ record[:status] ||= @status # for 500 errors
15
+ record[:request_id] = CurrentRequest.current.request_id
14
16
 
15
- # rand(500).times do |e|
16
- # finished = Time.now - rand(2000).minutes
17
- # record[:datetime] = finished.strftime(RailsPerformance::FORMAT)
18
- # record[:datetimei] = finished.to_i
19
- # record[:duration] = 50 + rand(100) + rand(50.0) + rand(e)
20
- # record[:db_runtime] = rand(50.0)
21
- # record[:view_runtime] = rand(50.0)
22
- # RP::Utils.log_request_in_redis(record)
23
- # end
17
+ # capture referer from where this page was opened
18
+ if record[:status] == 404
19
+ record[:HTTP_REFERER] = env["HTTP_REFERER"]
20
+ end
24
21
 
25
- RP::Utils.log_trace_in_redis(CurrentRequest.current.request_id, CurrentRequest.current.storage)
26
- RP::Utils.log_request_in_redis(record)
27
- Thread.current["RP_request_info"] = nil
28
- CurrentRequest.cleanup
22
+ # store for section "recent requests"
23
+ RP::Utils.log_trace_in_redis(CurrentRequest.current.request_id, CurrentRequest.current.storage)
24
+
25
+ # store request information
26
+ RP::Utils.log_request_in_redis(record)
27
+ ensure
28
+ # we don't want to have a memory leak
29
+ CurrentRequest.cleanup
30
+ end
29
31
  end
32
+ #puts "==> store performance data: #{(Time.now - t).round(3)}ms"
30
33
 
31
34
  [@status, @headers, @response]
32
35
  end
@@ -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
- value = e.slice(:view_runtime, :db_runtime, :duration)
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.0.1.18'
3
- end
2
+ VERSION = '0.9.2'
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.0.1.18
4
+ version: 0.9.2
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-10 00:00:00.000000000 Z
11
+ date: 2020-06-01 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: []