rails_performance 1.4.2 → 1.5.0

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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +40 -4
  3. data/app/assets/images/details.svg +3 -0
  4. data/app/assets/images/external.svg +1 -0
  5. data/app/controllers/rails_performance/base_controller.rb +4 -0
  6. data/app/views/rails_performance/javascripts/app.js +4 -1
  7. data/app/views/rails_performance/layouts/rails_performance.html.erb +3 -0
  8. data/app/views/rails_performance/rails_performance/_recent_row.html.erb +14 -2
  9. data/app/views/rails_performance/rails_performance/_summary.html.erb +9 -2
  10. data/app/views/rails_performance/rails_performance/crashes.html.erb +16 -2
  11. data/app/views/rails_performance/rails_performance/requests.html.erb +8 -1
  12. data/app/views/rails_performance/rails_performance/resources.html.erb +16 -43
  13. data/app/views/rails_performance/stylesheets/panel.css +14 -2
  14. data/app/views/rails_performance/stylesheets/style.css +40 -1
  15. data/lib/generators/rails_performance/install/templates/initializer.rb +3 -0
  16. data/lib/rails_performance/data_source.rb +1 -1
  17. data/lib/rails_performance/engine.rb +3 -3
  18. data/lib/rails_performance/events/record.rb +62 -0
  19. data/lib/rails_performance/gems/custom_ext.rb +1 -0
  20. data/lib/rails_performance/gems/delayed_job_ext.rb +1 -0
  21. data/lib/rails_performance/gems/grape_ext.rb +1 -0
  22. data/lib/rails_performance/gems/rake_ext.rb +1 -0
  23. data/lib/rails_performance/gems/sidekiq_ext.rb +1 -0
  24. data/lib/rails_performance/interface.rb +7 -0
  25. data/lib/rails_performance/models/resource_record.rb +3 -6
  26. data/lib/rails_performance/reports/annotations_report.rb +13 -0
  27. data/lib/rails_performance/reports/base_report.rb +2 -2
  28. data/lib/rails_performance/reports/resources_report.rb +22 -20
  29. data/lib/rails_performance/system_monitor/resource_chart.rb +105 -0
  30. data/lib/rails_performance/{extensions → system_monitor}/resources_monitor.rb +15 -36
  31. data/lib/rails_performance/utils.rb +6 -0
  32. data/lib/rails_performance/version.rb +2 -1
  33. data/lib/rails_performance.rb +15 -0
  34. metadata +10 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d387037eea90f299a14499105f68cd78794ee7a36122d45081c488b9ca3fb6dd
4
- data.tar.gz: 1c49de1b612f86f8089fdd2765b53bcbdddb2bb65d4890eacdf068f0ca14ecc3
3
+ metadata.gz: 10d13bdf7ddb45da1783c3665a64a89730639a3b13061c496e4502a59158c329
4
+ data.tar.gz: 738313bc41c6af0d1cfee519679519639acda8781dd4dd74602e3c55a6b1f6de
5
5
  SHA512:
6
- metadata.gz: 208578b1cc4a8a4b261c9607d89b045983ab2be0cc1ef811059be38a3c02dd4f9b333a0937900733b04ad8058c1c920c02f333bc31b9fae13c1629123456a99d
7
- data.tar.gz: 9066e380c0d9309554e7574298d8bda95570e4f0851fdfa586c675f998bcdfaed993decd336d9de4c6b1b6ed55b0065fc368aba8ba23c7627690db18bedae36b
6
+ metadata.gz: 33dcfe7183c335961e7716acc301b7c38877c9409c911ff0b6dfa0f5046ed40fc3f36fd9994cb8053367e467cb8c427eb042c218731cf7ada935fc6857c70b8d
7
+ data.tar.gz: e5e07d09bac167200fda9ff4e5b8d1bcd2e9558e4bc4a7bc9bf9c78d15f76d829f049c83793e7ef89c057bd83eda2ee2613ef6a01265a92aa6834dd891721047
data/README.md CHANGED
@@ -34,6 +34,7 @@ It allows you to track:
34
34
  - total duration of time spent per request, views rendering, DB
35
35
  - SQL queries, rendering logs in "Recent Requests" section
36
36
  - simple 500-crashes reports
37
+ - deployment events (or custom events)
37
38
  - Sidekiq jobs
38
39
  - Delayed Job jobs
39
40
  - Grape API inside Rails app
@@ -45,7 +46,7 @@ All data are stored in `local` Redis and not sent to any 3rd party servers.
45
46
 
46
47
  ## Production
47
48
 
48
- Gem is production-ready. At least on my 2 applications with ~800 unique users per day it works perfectly.
49
+ Gem is production-ready. At least in my 2 applications with ~800 unique users per day it works perfectly.
49
50
 
50
51
  Just don't forget to protect performance dashboard with http basic auth or check of current_user.
51
52
 
@@ -68,7 +69,7 @@ RailsPerformance.setup do |config|
68
69
  config.redis = Redis.new(url: ENV["REDIS_URL"].presence || "redis://127.0.0.1:6379/0") # or Redis::Namespace.new("rails-performance", redis: Redis.new), see below in README
69
70
  config.duration = 4.hours
70
71
 
71
- config.debug = false # currently not used>
72
+ config.debug = false # currently not used
72
73
  config.enabled = true
73
74
 
74
75
  # configure Recent tab (time window and limit of requests)
@@ -172,6 +173,7 @@ After installation and configuration, start your Rails application, make a few r
172
173
 
173
174
  If you, for whatever reason (company policy, devise, ...) need to mount RailsPerformance yourself, feel free to do so by using the following snippet as inspiration.
174
175
  You can skip the `mount_at` and `http_basic_authentication_*` configurations then, if you like.
176
+ Under certain constraints (i.e. subdomains) it may be necessary to set `url_options` in the config so that RailsPeformance can generate links correctly.
175
177
 
176
178
  ```ruby
177
179
  # config/routes.rb
@@ -223,10 +225,10 @@ env:
223
225
  RAILS_PERFORMANCE_SERVER_ID: "server"
224
226
  ```
225
227
 
226
- You can also specifify custom "context" and "role" for monitoring, by changing the env variables:
228
+ You can also specify custom "context" and "role" for monitoring, by changing the env variables:
227
229
 
228
230
  ```ruby
229
- RailsPerformance::Extensions::ResourceMonitor.new(
231
+ RailsPerformance::SystemMonitor::ResourcesMonitor.new(
230
232
  ENV["RAILS_PERFORMANCE_SERVER_CONTEXT"].presence || "rails",
231
233
  ENV["RAILS_PERFORMANCE_SERVER_ROLE"].presence || "web"
232
234
  )
@@ -236,6 +238,31 @@ More information here: `lib/rails_performance/engine.rb`.
236
238
 
237
239
  PS: right now it can only distinguish between web app servers and the sidekiq servers.
238
240
 
241
+ #### Deployment Events + Custom Events on the Charts
242
+
243
+ ![Deployments](docs/deploys.png)
244
+
245
+ #### For Kamal
246
+
247
+ - edit `.kamal/hooks/post-deploy` (rename .sample if needed)
248
+ - add `kamal app exec -p './bin/rails runner "RailsPerformance.create_event(name: \"Deploy\")"'`
249
+ - kamal deploy
250
+
251
+ #### Custom Events on the Charts
252
+
253
+ You can specify colors, orientation for the event label.
254
+
255
+ ```ruby
256
+ RailsPerformance.create_event(name: "Deploy", options: {
257
+ borderColor: "#00E396",
258
+ label: {
259
+ borderColor: "#00E396",
260
+ orientation: "horizontal",
261
+ text: "Deploy"
262
+ }
263
+ })
264
+ ```
265
+
239
266
  ### Custom events
240
267
 
241
268
  ```ruby
@@ -323,6 +350,15 @@ The idea of this gem grew from curiosity how many RPM my app receiving per day.
323
350
  - sinatra?
324
351
  - tests to check what is actually stored in redis db after request
325
352
  - upgrade bulma
353
+ - optimize svg icons, remove inline svg (switch to base64 or assets)
354
+
355
+ ## Development
356
+
357
+ 1. Clone the repo
358
+ 2. Run `bundle install`
359
+ 3. Setup dummy app `cd test/dummy && bundle install && rails db:create && rails db:migrate`
360
+ 4. Run `rails s` in the root folder
361
+ 5. Run `rails test` to run tests
326
362
 
327
363
  ## Contributing
328
364
 
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
2
+ <path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607ZM10.5 7.5v6m3-3h-6" />
3
+ </svg>
@@ -0,0 +1 @@
1
+ <?xml version="1.0"?><svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g><path d="M0 0h24v24H0z" fill="none"/><path d="M10 6v2H5v11h11v-5h2v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h6zm11-3v8h-2V6.413l-7.793 7.794-1.414-1.414L17.585 5H13V3h8z"/></g></svg>
@@ -12,6 +12,10 @@ module RailsPerformance
12
12
  password: RailsPerformance.http_basic_authentication_password
13
13
  end
14
14
 
15
+ def url_options
16
+ RailsPerformance.url_options.nil? ? super : RailsPerformance.url_options
17
+ end
18
+
15
19
  private
16
20
 
17
21
  def verify_access
@@ -6,6 +6,7 @@ function showChart(element_id, type, title, options) {
6
6
  chart: {
7
7
  type: type,
8
8
  height: 300,
9
+ width: '100%',
9
10
  zoom: {
10
11
  type: 'x',
11
12
  },
@@ -78,7 +79,8 @@ function showTIRChart(element_id, data, addon, name) {
78
79
  series: [{
79
80
  name: name,
80
81
  data: data
81
- }]
82
+ }],
83
+ annotations: window?.annotationsData || {}
82
84
  });
83
85
  }
84
86
 
@@ -89,6 +91,7 @@ function showRTChart(element_id, data) {
89
91
  name: 'Response Time',
90
92
  data: data,
91
93
  }],
94
+ annotations: window?.annotationsData || {}
92
95
  });
93
96
  }
94
97
 
@@ -8,6 +8,9 @@
8
8
  <%= csp_meta_tag if ::Rails::VERSION::STRING.to_f >= 5.2 %>
9
9
  <%= render '/rails_performance/stylesheets/stylesheets' %>
10
10
  <link rel="shortcut icon" href="/favicon.ico">
11
+ <script>
12
+ window.annotationsData = <%= raw RailsPerformance::Reports::AnnotationsReport.new.data.to_json %>;
13
+ </script>
11
14
  </head>
12
15
 
13
16
  <body class="has-sticky-footer">
@@ -9,12 +9,24 @@
9
9
  <td><%= format_datetime e[:datetime] %></td>
10
10
  <td>
11
11
  <% controller_action_info = e[:controller] + '#' + e[:action]%>
12
- <%= link_to truncate(controller_action_info, length: 40), rails_performance.rails_performance_summary_path({controller_eq: e[:controller], action_eq: e[:action]}), remote: true, title: controller_action_info %>
12
+ <%= link_to rails_performance.rails_performance_summary_path({controller_eq: e[:controller], action_eq: e[:action]}), remote: true, title: controller_action_info do %>
13
+ <span class="details_icon">
14
+ <%= icon 'details' %>
15
+ </span>
16
+ <%= truncate(controller_action_info, length: 40) %>
17
+ <% end %>
13
18
  </td>
14
19
  <td><%= e[:method] %></td>
15
20
  <td><%= e[:format] %></td>
16
21
  <td><%= bot_icon e["user_agent"] %></td>
17
- <td><%= link_to_path(e) %></td>
22
+ <td>
23
+ <span class="with_external_icon">
24
+ <%= link_to_path(e) %>
25
+ <span class="icon external_icon">
26
+ <%= icon('external') %>
27
+ </span>
28
+ </span>
29
+ </td>
18
30
  <td><%= status_tag e[:status] %></td>
19
31
  <td class="nowrap"><%= ms e[:duration] %></td>
20
32
  <td class="nowrap"><%= ms e[:view_runtime] %></td>
@@ -5,7 +5,7 @@
5
5
  <div id="response_time_report_chart_mini" class="chart_mini"></div>
6
6
 
7
7
  <h2 class="subtitle"><%= title %></h2>
8
- <table class="table is-fullwidth is-hoverable is-narrow is-size-9">
8
+ <table class="table is-fullwidth is-hoverable is-narrow is-size-8">
9
9
  <thead>
10
10
  <tr>
11
11
  <th data-sort="string">Datetime</th>
@@ -25,7 +25,14 @@
25
25
  <td><%= format_datetime e[:datetime] %></td>
26
26
  <td><%= e[:method] %></td>
27
27
  <td><%= bot_icon e["user_agent"] %></td>
28
- <td><%= link_to_path(e) %></td>
28
+ <td>
29
+ <span class="with_external_icon">
30
+ <%= link_to_path(e) %>
31
+ <span class="icon external_icon">
32
+ <%= icon('external') %>
33
+ </span>
34
+ </span>
35
+ </td>
29
36
  <td><%= e[:format] %></td>
30
37
  <td><%= status_tag e[:status] %></td>
31
38
  <td class="nowrap"><%= ms e[:duration] %></td>
@@ -35,11 +35,25 @@
35
35
  <% @data.each do |e| %>
36
36
  <tr>
37
37
  <td><%= format_datetime e[:datetime] %></td>
38
- <td><%= link_to e[:controller] + '#' + e[:action], rails_performance.rails_performance_summary_path({controller_eq: e[:controller], action_eq: e[:action]}), remote: true %></td>
38
+ <td>
39
+ <%= link_to rails_performance.rails_performance_summary_path({controller_eq: e[:controller], action_eq: e[:action]}), remote: true do %>
40
+ <span class="details_icon">
41
+ <%= icon 'details' %>
42
+ </span>
43
+ <%= e[:controller] + '#' + e[:action] %>
44
+ <% end %>
45
+ </td>
39
46
  <td><%= e[:method] %></td>
40
47
  <td><%= e[:format] %></td>
41
48
  <td><%= bot_icon e["user_agent"] %></td>
42
- <td><%= link_to_path(e) %></td>
49
+ <td>
50
+ <span class="with_external_icon">
51
+ <%= link_to_path(e) %>
52
+ <span class="icon external_icon">
53
+ <%= icon('external') %>
54
+ </span>
55
+ </span>
56
+ </td>
43
57
  <td><%= e[:exception] %></td>
44
58
  <td class="very-small-text">
45
59
  <%= raw e[:backtrace]&.join("<br/>") %>
@@ -36,7 +36,14 @@
36
36
  <% groups = e[:group].split("|") %>
37
37
  <% c, a = groups[0].split("#") %>
38
38
  <tr>
39
- <td><%= link_to groups[0], rails_performance.rails_performance_summary_path({controller_eq: c, action_eq: a}), remote: true %></td>
39
+ <td>
40
+ <%= link_to rails_performance.rails_performance_summary_path({controller_eq: c, action_eq: a}), remote: true do %>
41
+ <span class="details_icon">
42
+ <%= icon 'details' %>
43
+ </span>
44
+ <%= groups[0] %>
45
+ <% end %>
46
+ </td>
40
47
  <td><%= link_to groups[1].try(:upcase), rails_performance.rails_performance_summary_path({controller_eq: c, action_eq: a, format_eq: groups[1]}), remote: true %></td>
41
48
  <td><%= e[:count] %></td>
42
49
  <td class="nowrap attention"><%= ms e[:p50_duration] %></td>
@@ -1,50 +1,23 @@
1
1
  <title>System Resources</title>
2
2
 
3
- <% @resources_report.data.keys.each do |server_key| %>
4
- <h1 class="title mt-8 pt-8"><%= server_key.split("///"). join(", ") %></h1>
5
-
6
- <div class="card">
7
- <div class="card-content">
8
- <h2 class="subtitle">CPU</h2>
9
- <div id="cpu_report_<%= server_key.parameterize %>" class="chart"></div>
10
- <p class="content is-small">CPU usage %, average per 1 minute</p>
11
- </div>
12
- </div>
13
-
14
- <br/>
15
-
16
- <div class="card">
17
- <div class="card-content">
18
- <h2 class="subtitle">Memory</h2>
19
- <div id="memory_report_<%= server_key.parameterize %>" class="chart"></div>
20
- <p class="content is-small">App memory usage</p>
21
- </div>
22
- </div>
23
-
24
- <br/>
25
-
26
- <div class="card">
27
- <div class="card-content">
28
- <h2 class="subtitle">Storage</h2>
29
- <div id="disk_report_<%= server_key.parameterize %>" class="chart"></div>
30
- <p class="content is-small">Available storage size (local disk size)</p>
3
+ <% @resources_report.servers.each do |server| %>
4
+ <h1 class="title mt-8 pt-8"><%= server.name %></h1>
5
+
6
+ <% server.charts.each.with_index do |chart, index| %>
7
+ <div class="card">
8
+ <div class="card-content">
9
+ <h2 class="subtitle"><%= chart.subtitle %></h2>
10
+ <div class="chart" id="<%= chart.id %>"></div>
11
+ <p class="content is-small"><%= chart.description %></p>
12
+ </div>
31
13
  </div>
32
- </div>
33
-
34
- <br>
35
- <% end %>
36
-
37
- <% content_for :on_load do %>
38
- <script>
39
- <% @resources_report.data.keys.each do |server_key| %>
40
- var data1 = <%= raw @resources_report.cpu[server_key].to_json %>;
41
- showPercentageChart('cpu_report_<%= server_key.parameterize %>', data1, 'CPU');
42
14
 
43
- var data2 = <%= raw @resources_report.memory[server_key].to_json %>;
44
- showUsageChart('memory_report_<%= server_key.parameterize %>', data2, 'Usage', 2);
15
+ <br/>
45
16
 
46
- var data3 = <%= raw @resources_report.disk[server_key].to_json %>;
47
- showUsageChart('disk_report_<%= server_key.parameterize %>', data3, 'Available', 3);
17
+ <% content_for :on_load do %>
18
+ <script>
19
+ show<%= chart.type %>Chart('<%= chart.id %>', <%= raw chart.data.to_json %>, '<%= chart.legend %>', <%= index %>);
20
+ </script>
48
21
  <% end %>
49
- </script>
22
+ <% end %>
50
23
  <% end %>
@@ -32,13 +32,25 @@
32
32
  .cd-panel__container {
33
33
  z-index: 100;
34
34
  position: fixed;
35
- width: 800px;
35
+ width: 1200px;
36
36
  height: 100%;
37
37
  top: 0;
38
38
  transition: transform 0.3s 0s;
39
39
  overflow: scroll;
40
40
  }
41
41
 
42
+ @media (max-width: 1200px) {
43
+ .cd-panel__container {
44
+ width: 800px;
45
+ }
46
+ }
47
+
48
+ @media (max-width: 768px) {
49
+ .cd-panel__container {
50
+ width: 500px;
51
+ }
52
+ }
53
+
42
54
  .cd-panel--from-right .cd-panel__container {
43
55
  right: 0;
44
56
  transform: translate3d(100%, 0, 0);
@@ -57,4 +69,4 @@
57
69
  .cd-panel__container .panel {
58
70
  background: white;
59
71
  min-height: 100vh;
60
- }
72
+ }
@@ -62,6 +62,33 @@
62
62
  color: red;
63
63
  }
64
64
 
65
+ .external_icon svg {
66
+ width: 12px;
67
+ height: 12px;
68
+ color: #eee;
69
+ margin-left: -4px;
70
+ opacity: 0;
71
+ }
72
+
73
+ .details_icon {
74
+ position: relative;
75
+ width: 14px;
76
+ height: 14px;
77
+ }
78
+
79
+ .details_icon svg {
80
+ position: relative;
81
+ top: 2px;
82
+ width: 14px;
83
+ height: 14px;
84
+ }
85
+
86
+ .with_external_icon:hover {
87
+ .external_icon svg {
88
+ opacity: 1;
89
+ }
90
+ }
91
+
65
92
  .user-agent-icon svg {
66
93
  width: 12px;
67
94
  height: 12px;
@@ -77,7 +104,19 @@
77
104
 
78
105
  .chart_mini {
79
106
  height: 200px;
80
- width: 720px;
107
+ width: 1160px;
108
+ }
109
+
110
+ @media (max-width: 1200px) {
111
+ .chart_mini {
112
+ width: 770px;
113
+ }
114
+ }
115
+
116
+ @media (max-width: 768px) {
117
+ .chart_mini {
118
+ width: 400px;
119
+ }
81
120
  }
82
121
 
83
122
  .red {
@@ -31,6 +31,9 @@ if defined?(RailsPerformance)
31
31
  # for example when you have `current_user`
32
32
  # config.verify_access_proc = proc { |controller| controller.current_user && controller.current_user.admin? }
33
33
 
34
+ # Override engine url options, necessary if hosting under a unique domain
35
+ # config.url_options = {host: "sub.example.com"}
36
+
34
37
  # You can ignore endpoints with Rails standard notation controller#action
35
38
  # config.ignored_endpoints = ['HomeController#contact']
36
39
 
@@ -22,7 +22,7 @@ module RailsPerformance
22
22
 
23
23
  def db
24
24
  result = RailsPerformance::Models::Collection.new
25
- now = RailsPerformance::Utils.time
25
+ now = RailsPerformance::Utils.kind_of_now
26
26
  (0..days).to_a.reverse_each do |e|
27
27
  RailsPerformance::DataSource.new(q: q.merge({on: (now - e.days).to_date}), type: type).add_to(result)
28
28
  end
@@ -2,7 +2,7 @@ require "action_view/log_subscriber"
2
2
  require_relative "rails/middleware"
3
3
  require_relative "models/collection"
4
4
  require_relative "instrument/metrics_collector"
5
- require_relative "extensions/resources_monitor"
5
+ require_relative "system_monitor/resources_monitor"
6
6
 
7
7
  module RailsPerformance
8
8
  class Engine < ::Rails::Engine
@@ -16,7 +16,7 @@ module RailsPerformance
16
16
  next if $rails_performance_running_mode == :console # rubocop:disable Style/GlobalVars
17
17
 
18
18
  # start monitoring
19
- RailsPerformance._resource_monitor = RailsPerformance::Extensions::ResourceMonitor.new(
19
+ RailsPerformance._resource_monitor = RailsPerformance::SystemMonitor::ResourcesMonitor.new(
20
20
  ENV["RAILS_PERFORMANCE_SERVER_CONTEXT"].presence || "rails",
21
21
  ENV["RAILS_PERFORMANCE_SERVER_ROLE"].presence || "web"
22
22
  )
@@ -44,7 +44,7 @@ module RailsPerformance
44
44
  RailsPerformance._resource_monitor.stop_monitoring
45
45
  RailsPerformance._resource_monitor = nil
46
46
  # start background monitoring
47
- RailsPerformance._resource_monitor = RailsPerformance::Extensions::ResourceMonitor.new(
47
+ RailsPerformance._resource_monitor = RailsPerformance::SystemMonitor::ResourcesMonitor.new(
48
48
  ENV["RAILS_PERFORMANCE_SERVER_CONTEXT"].presence || "sidekiq",
49
49
  ENV["RAILS_PERFORMANCE_SERVER_ROLE"].presence || "background"
50
50
  )
@@ -0,0 +1,62 @@
1
+ module RailsPerformance
2
+ module Events
3
+ class Record
4
+ attr_reader :name, :datetimei, :options
5
+
6
+ DEFAULT_COLOR = "#FF00FF"
7
+ DEFAULT_LABEL_COLOR = "#FF00FF"
8
+ DEFAULT_LABEL_ORIENTATION = "horizontal"
9
+
10
+ class << self
11
+ def create(name:, datetimei: Time.now.to_i, options: {})
12
+ instance = new(name: name, datetimei: datetimei, options: options)
13
+ instance.save
14
+ instance
15
+ end
16
+
17
+ def all
18
+ _, values = RailsPerformance::Utils.fetch_from_redis("rails_performance:records:events:*")
19
+ Array(values).map do |value|
20
+ json = JSON.parse(value)
21
+ new(name: json["name"], datetimei: json["datetimei"], options: Hash(json["options"]))
22
+ end
23
+ end
24
+ end
25
+
26
+ def initialize(name:, datetimei:, options: {})
27
+ @name = name
28
+ @datetimei = datetimei
29
+ @options = options
30
+ end
31
+
32
+ def save
33
+ RailsPerformance::Utils.save_to_redis(rails_performance_key, value)
34
+ end
35
+
36
+ def rails_performance_key
37
+ "rails_performance:records:events:#{datetimei}|#{RailsPerformance::EVENTS_SCHEMA}"
38
+ end
39
+
40
+ def value
41
+ {
42
+ name: name,
43
+ datetime: RailsPerformance::Utils.from_datetimei(datetimei.to_i),
44
+ datetimei: datetimei,
45
+ options: options
46
+ }
47
+ end
48
+
49
+ def to_annotation
50
+ {
51
+ x: datetimei * 1000,
52
+ borderColor: options.dig("borderColor") || DEFAULT_COLOR,
53
+ label: {
54
+ borderColor: options.dig("label", "borderColor") || DEFAULT_LABEL_COLOR,
55
+ orientation: options.dig("label", "orientation") || DEFAULT_LABEL_ORIENTATION,
56
+ text: options.dig("label", "text") || name
57
+ }
58
+ }
59
+ end
60
+ end
61
+ end
62
+ end
@@ -24,6 +24,7 @@ module RailsPerformance
24
24
  datetime: now.strftime(RailsPerformance::FORMAT),
25
25
  datetimei: now.to_i
26
26
  ).save
27
+ CurrentRequest.cleanup
27
28
  end
28
29
  end
29
30
  end
@@ -23,6 +23,7 @@ module RailsPerformance
23
23
  status: status
24
24
  )
25
25
  record.save
26
+ CurrentRequest.cleanup
26
27
  end
27
28
  end
28
29
 
@@ -25,6 +25,7 @@ module RailsPerformance
25
25
 
26
26
  if name == "format_response.grape"
27
27
  CurrentRequest.current.record.save
28
+ CurrentRequest.cleanup
28
29
  end
29
30
  end
30
31
  end
@@ -23,6 +23,7 @@ module RailsPerformance
23
23
  duration: (RailsPerformance::Utils.time - now) * 1000,
24
24
  status: status
25
25
  ).save
26
+ CurrentRequest.cleanup
26
27
  end
27
28
  end
28
29
 
@@ -27,6 +27,7 @@ module RailsPerformance
27
27
  # store in ms instead of seconds
28
28
  record.duration = (RailsPerformance::Utils.time - now) * 1000
29
29
  record.save
30
+ CurrentRequest.cleanup
30
31
  end
31
32
  end
32
33
  end
@@ -0,0 +1,7 @@
1
+ module RailsPerformance
2
+ module Interface
3
+ def create_event(name:, datetime: RailsPerformance::Utils.time, options: {})
4
+ RailsPerformance::Events::Record.create(name: name, datetimei: datetime.to_i, options: options)
5
+ end
6
+ end
7
+ end
@@ -26,16 +26,13 @@ module RailsPerformance
26
26
  end
27
27
 
28
28
  def record_hash
29
- {
29
+ value.symbolize_keys.merge({
30
30
  server: server,
31
31
  role: role,
32
32
  context: context,
33
33
  datetime: datetime,
34
- datetimei: RailsPerformance::Utils.from_datetimei(datetimei.to_i),
35
- cpu: value["cpu"],
36
- memory: value["memory"],
37
- disk: value["disk"]
38
- }
34
+ datetimei: RailsPerformance::Utils.from_datetimei(datetimei.to_i)
35
+ })
39
36
  end
40
37
 
41
38
  def save
@@ -0,0 +1,13 @@
1
+ class RailsPerformance::Reports::AnnotationsReport
2
+ def data
3
+ {
4
+ xaxis: xaxis
5
+ }
6
+ end
7
+
8
+ private
9
+
10
+ def xaxis
11
+ RailsPerformance::Events::Record.all.map(&:to_annotation)
12
+ end
13
+ end
@@ -32,7 +32,7 @@ module RailsPerformance
32
32
 
33
33
  # TODO: simplify this method, and combine with nullify_data
34
34
  def calculate_data
35
- now = RailsPerformance::Utils.time
35
+ now = RailsPerformance::Utils.kind_of_now
36
36
  now = now.change(sec: 0, usec: 0)
37
37
  stop = now # Time.at(60 * (now.to_i / 60), in:)
38
38
  offset = 0 # RailsPerformance::Reports::BaseReport.time_in_app_time_zone(now).utc_offset
@@ -66,7 +66,7 @@ module RailsPerformance
66
66
  def nil_data(duration = RailsPerformance.duration)
67
67
  @nil_data ||= begin
68
68
  result = {}
69
- now = RailsPerformance::Utils.time
69
+ now = RailsPerformance::Utils.kind_of_now
70
70
  now = now.change(sec: 0, usec: 0)
71
71
  stop = now # Time.at(60 * (now.to_i / 60))
72
72
  offset = 0 # RailsPerformance::Reports::BaseReport.time_in_app_time_zone(now).utc_offset
@@ -1,40 +1,42 @@
1
1
  module RailsPerformance
2
2
  module Reports
3
3
  class ResourcesReport < BaseReport
4
- def data
5
- @data ||= db.data
6
- .collect { |e| e.record_hash }
7
- .group_by { |e| e[:server] + "///" + e[:context] + "///" + e[:role] }
8
- # .transform_values { |v| v.sort { |a, b| b[sort] <=> a[sort] } }
9
- .transform_values { |v| v.map { |e| e.merge({datetimei: e[:datetimei].to_i}) } }
10
- end
4
+ Server = Struct.new(:report, :key) do
5
+ def name
6
+ key.split("///").join(", ")
7
+ end
11
8
 
12
- def cpu
13
- @cpu ||= data.transform_values do |v|
14
- prepare_report(v.each_with_object({}) do |e, res|
15
- res[e[:datetimei] * 1000] = e[:cpu]["one_min"].to_f.round(2)
16
- end)
9
+ def charts
10
+ RailsPerformance.system_monitors.map do |class_name|
11
+ SystemMonitor.const_get(class_name).new(self)
12
+ end
17
13
  end
18
14
  end
19
15
 
20
- def memory
21
- @memory ||= data.transform_values do |v|
22
- prepare_report(v.each_with_object({}) do |e, res|
23
- res[e[:datetimei] * 1000] = e[:memory].to_f.round(2)
24
- end)
16
+ def servers
17
+ data.keys.map do |key|
18
+ Server.new(self, key)
25
19
  end
26
20
  end
27
21
 
28
- def disk
29
- @disk ||= data.transform_values do |v|
22
+ def extract_signal &block
23
+ data.transform_values do |v|
30
24
  prepare_report(v.each_with_object({}) do |e, res|
31
- res[e[:datetimei] * 1000] = e[:disk]["available"].to_f.round(2)
25
+ res[e[:datetimei] * 1000] = block.call(e)
32
26
  end)
33
27
  end
34
28
  end
35
29
 
36
30
  private
37
31
 
32
+ def data
33
+ @data ||= db.data
34
+ .collect { |e| e.record_hash }
35
+ .group_by { |e| e[:server] + "///" + e[:context] + "///" + e[:role] }
36
+ # .transform_values { |v| v.sort { |a, b| b[sort] <=> a[sort] } }
37
+ .transform_values { |v| v.map { |e| e.merge({datetimei: e[:datetimei].to_i}) } }
38
+ end
39
+
38
40
  def prepare_report(input)
39
41
  nullify_data(input, RailsPerformance.system_monitor_duration)
40
42
  end
@@ -0,0 +1,105 @@
1
+ module RailsPerformance
2
+ module SystemMonitor
3
+ ResourceChart = Struct.new(:server, :key, :type, :subtitle, :description, :legend, keyword_init: true) do
4
+ def id
5
+ [key, "report", server.key.parameterize].join("_")
6
+ end
7
+
8
+ def data
9
+ all_data = server.report.extract_signal { |e| signal(e) }
10
+ all_data[server.key]
11
+ end
12
+
13
+ def signal e
14
+ format(e[key])
15
+ end
16
+
17
+ def format measurement
18
+ measurement
19
+ end
20
+ end
21
+
22
+ class CPULoad < ResourceChart
23
+ def initialize server
24
+ super(
25
+ server:,
26
+ key: :cpu,
27
+ type: "Percentage",
28
+ subtitle: "CPU",
29
+ description: "CPU load average (1 min), average per 1 minute",
30
+ legend: "CPU",
31
+ )
32
+ end
33
+
34
+ def format measurement
35
+ measurement["one_min"].to_f.round(2)
36
+ end
37
+
38
+ def measure
39
+ load_averages = Sys::CPU.load_avg
40
+ {
41
+ one_min: load_averages[0],
42
+ five_min: load_averages[1],
43
+ fifteen_min: load_averages[2]
44
+ }
45
+ rescue => e
46
+ ::Rails.logger.error "Error fetching CPU usage: #{e.message}"
47
+ {one_min: 0.0, five_min: 0.0, fifteen_min: 0.0}
48
+ end
49
+ end
50
+
51
+ class MemoryUsage < ResourceChart
52
+ def initialize server
53
+ super(
54
+ server:,
55
+ key: :memory,
56
+ type: "Usage",
57
+ subtitle: "Memory",
58
+ description: "App memory usage",
59
+ legend: "Usage",
60
+ )
61
+ end
62
+
63
+ def format measurement
64
+ measurement.to_f.round(2)
65
+ end
66
+
67
+ def measure
68
+ GetProcessMem.new.bytes
69
+ rescue => e
70
+ ::Rails.logger.error "Error fetching memory usage: #{e.message}"
71
+ 0
72
+ end
73
+ end
74
+
75
+ class DiskUsage < ResourceChart
76
+ def initialize server
77
+ super(
78
+ server:,
79
+ key: :disk,
80
+ type: "Usage",
81
+ subtitle: "Storage",
82
+ description: "Available storage size (local disk size)",
83
+ legend: "Usage",
84
+ )
85
+ end
86
+
87
+ def signal measurement
88
+ measurement["available"].to_f.round(2)
89
+ end
90
+
91
+ def measure
92
+ path = "/"
93
+ stat = Sys::Filesystem.stat(path)
94
+ {
95
+ available: stat.blocks_available * stat.block_size,
96
+ total: stat.blocks * stat.block_size,
97
+ used: (stat.blocks - stat.blocks_available) * stat.block_size
98
+ }
99
+ rescue => e
100
+ ::Rails.logger.error "Error fetching disk space: #{e.message}"
101
+ {available: 0, total: 0, used: 0}
102
+ end
103
+ end
104
+ end
105
+ end
@@ -1,6 +1,8 @@
1
+ require "rails_performance/system_monitor/resource_chart"
2
+
1
3
  module RailsPerformance
2
- module Extensions
3
- class ResourceMonitor
4
+ module SystemMonitor
5
+ class ResourcesMonitor
4
6
  attr_reader :context, :role
5
7
 
6
8
  def initialize(context, role)
@@ -39,49 +41,26 @@ module RailsPerformance
39
41
  end
40
42
  end
41
43
 
42
- def run
43
- cpu = fetch_process_cpu_usage
44
- memory = fetch_process_memory_usage
45
- disk = fetch_disk_usage
46
-
47
- store_data({cpu: cpu, memory: memory, disk: disk})
48
- end
49
-
50
- def fetch_process_cpu_usage
51
- load_averages = Sys::CPU.load_avg
52
- {
53
- one_min: load_averages[0],
54
- five_min: load_averages[1],
55
- fifteen_min: load_averages[2]
56
- }
57
- rescue => e
58
- ::Rails.logger.error "Error fetching CPU usage: #{e.message}"
59
- {one_min: 0.0, five_min: 0.0, fifteen_min: 0.0}
44
+ def payload
45
+ monitors.reduce({}) do |data, monitor|
46
+ data.merge(monitor.key => monitor.measure)
47
+ end
60
48
  end
61
49
 
62
- def fetch_process_memory_usage
63
- GetProcessMem.new.bytes
64
- rescue => e
65
- ::Rails.logger.error "Error fetching memory usage: #{e.message}"
66
- 0
50
+ def monitors
51
+ @monitors ||= RailsPerformance.system_monitors.map do |class_name|
52
+ RailsPerformance::SystemMonitor.const_get(class_name).new(nil)
53
+ end
67
54
  end
68
55
 
69
- def fetch_disk_usage(path = "/")
70
- stat = Sys::Filesystem.stat(path)
71
- {
72
- available: stat.blocks_available * stat.block_size,
73
- total: stat.blocks * stat.block_size,
74
- used: (stat.blocks - stat.blocks_available) * stat.block_size
75
- }
76
- rescue => e
77
- ::Rails.logger.error "Error fetching disk space: #{e.message}"
78
- {available: 0, total: 0, used: 0}
56
+ def run
57
+ store_data(payload)
79
58
  end
80
59
 
81
60
  def store_data(data)
82
61
  ::Rails.logger.info("Server: #{server_id}, Context: #{context}, Role: #{role}, data: #{data}")
83
62
 
84
- now = RailsPerformance::Utils.time
63
+ now = RailsPerformance::Utils.kind_of_now
85
64
  now = now.change(sec: 0, usec: 0)
86
65
  RailsPerformance::Models::ResourceRecord.new(
87
66
  server: server_id,
@@ -1,9 +1,15 @@
1
1
  module RailsPerformance
2
2
  class Utils
3
+ DEFAULT_TIME_OFFSET = 1.minute
4
+
3
5
  def self.time
4
6
  Time.now.utc
5
7
  end
6
8
 
9
+ def self.kind_of_now
10
+ time + DEFAULT_TIME_OFFSET
11
+ end
12
+
7
13
  def self.from_datetimei(datetimei)
8
14
  Time.at(datetimei, in: "+00:00")
9
15
  end
@@ -1,4 +1,5 @@
1
1
  module RailsPerformance
2
- VERSION = "1.4.2"
2
+ VERSION = "1.5.0"
3
3
  SCHEMA = "1.0.2"
4
+ EVENTS_SCHEMA = "1.0.0"
4
5
  end
@@ -26,10 +26,15 @@ require_relative "rails_performance/reports/breakdown_report"
26
26
  require_relative "rails_performance/reports/trace_report"
27
27
  require_relative "rails_performance/reports/percentile_report"
28
28
  require_relative "rails_performance/reports/resources_report"
29
+ require_relative "rails_performance/reports/annotations_report"
30
+ require_relative "rails_performance/events/record"
29
31
  require_relative "rails_performance/extensions/trace"
30
32
  require_relative "rails_performance/thread/current_request"
33
+ require_relative "rails_performance/interface"
31
34
 
32
35
  module RailsPerformance
36
+ extend RailsPerformance::Interface
37
+
33
38
  FORMAT = "%Y%m%dT%H%M"
34
39
 
35
40
  mattr_accessor :redis
@@ -119,10 +124,20 @@ module RailsPerformance
119
124
  mattr_accessor :ignore_trace_headers
120
125
  @@ignore_trace_headers = ["datetimei"]
121
126
 
127
+ mattr_accessor :url_options
128
+ @@url_options = nil
129
+
122
130
  # System monitor duration (expiration time)
123
131
  mattr_accessor :system_monitor_duration
124
132
  @@system_monitor_duration = 24.hours
125
133
 
134
+ mattr_accessor :system_monitors
135
+ @@system_monitors = [
136
+ "CPULoad",
137
+ "MemoryUsage",
138
+ "DiskUsage"
139
+ ]
140
+
126
141
  # -- internal usage --
127
142
  #
128
143
  #
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_performance
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.2
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Igor Kasyanchuk
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-05-28 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: railties
@@ -290,8 +290,10 @@ files:
290
290
  - app/assets/images/activity.svg
291
291
  - app/assets/images/bot.svg
292
292
  - app/assets/images/close.svg
293
+ - app/assets/images/details.svg
293
294
  - app/assets/images/download.svg
294
295
  - app/assets/images/export.svg
296
+ - app/assets/images/external.svg
295
297
  - app/assets/images/git.svg
296
298
  - app/assets/images/github.svg
297
299
  - app/assets/images/home.svg
@@ -345,7 +347,7 @@ files:
345
347
  - lib/rails_performance.rb
346
348
  - lib/rails_performance/data_source.rb
347
349
  - lib/rails_performance/engine.rb
348
- - lib/rails_performance/extensions/resources_monitor.rb
350
+ - lib/rails_performance/events/record.rb
349
351
  - lib/rails_performance/extensions/trace.rb
350
352
  - lib/rails_performance/gems/custom_ext.rb
351
353
  - lib/rails_performance/gems/delayed_job_ext.rb
@@ -353,6 +355,7 @@ files:
353
355
  - lib/rails_performance/gems/rake_ext.rb
354
356
  - lib/rails_performance/gems/sidekiq_ext.rb
355
357
  - lib/rails_performance/instrument/metrics_collector.rb
358
+ - lib/rails_performance/interface.rb
356
359
  - lib/rails_performance/models/base_record.rb
357
360
  - lib/rails_performance/models/collection.rb
358
361
  - lib/rails_performance/models/custom_record.rb
@@ -365,6 +368,7 @@ files:
365
368
  - lib/rails_performance/models/trace_record.rb
366
369
  - lib/rails_performance/rails/middleware.rb
367
370
  - lib/rails_performance/rails/query_builder.rb
371
+ - lib/rails_performance/reports/annotations_report.rb
368
372
  - lib/rails_performance/reports/base_report.rb
369
373
  - lib/rails_performance/reports/breakdown_report.rb
370
374
  - lib/rails_performance/reports/crash_report.rb
@@ -376,6 +380,8 @@ files:
376
380
  - lib/rails_performance/reports/slow_requests_report.rb
377
381
  - lib/rails_performance/reports/throughput_report.rb
378
382
  - lib/rails_performance/reports/trace_report.rb
383
+ - lib/rails_performance/system_monitor/resource_chart.rb
384
+ - lib/rails_performance/system_monitor/resources_monitor.rb
379
385
  - lib/rails_performance/thread/current_request.rb
380
386
  - lib/rails_performance/utils.rb
381
387
  - lib/rails_performance/version.rb
@@ -397,7 +403,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
397
403
  - !ruby/object:Gem::Version
398
404
  version: '0'
399
405
  requirements: []
400
- rubygems_version: 3.6.3
406
+ rubygems_version: 3.6.9
401
407
  specification_version: 4
402
408
  summary: Simple Rails Performance tracker. Alternative to the NewRelic, Datadog or
403
409
  other services.