rails_performance 1.3.3 → 1.4.0.alpha1

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.
data/config/routes.rb CHANGED
@@ -14,6 +14,7 @@ RailsPerformance::Engine.routes.draw do
14
14
  get "/grape" => "rails_performance#grape", :as => :rails_performance_grape
15
15
  get "/rake" => "rails_performance#rake", :as => :rails_performance_rake
16
16
  get "/custom" => "rails_performance#custom", :as => :rails_performance_custom
17
+ get "/resources" => "rails_performance#resources", :as => :rails_performance_resources
17
18
  end
18
19
 
19
20
  Rails.application.routes.draw do
@@ -6,7 +6,8 @@ module RailsPerformance
6
6
  delayed_job: RailsPerformance::Models::DelayedJobRecord,
7
7
  grape: RailsPerformance::Models::GrapeRecord,
8
8
  rake: RailsPerformance::Models::RakeRecord,
9
- custom: RailsPerformance::Models::CustomRecord
9
+ custom: RailsPerformance::Models::CustomRecord,
10
+ resources: RailsPerformance::Models::ResourceRecord
10
11
  }
11
12
 
12
13
  attr_reader :q, :klass, :type
@@ -20,8 +21,9 @@ module RailsPerformance
20
21
 
21
22
  def db
22
23
  result = RailsPerformance::Models::Collection.new
23
- (0..(RailsPerformance::Utils.days + 1)).to_a.reverse_each do |e|
24
- RailsPerformance::DataSource.new(q: q.merge({on: (Time.current - e.days).to_date}), type: type).add_to(result)
24
+ now = Time.current
25
+ (0..(RailsPerformance::Utils.days)).to_a.reverse_each do |e|
26
+ RailsPerformance::DataSource.new(q: q.merge({on: (now - e.days).to_date}), type: type).add_to(result)
25
27
  end
26
28
  result
27
29
  end
@@ -53,6 +55,8 @@ module RailsPerformance
53
55
  case type
54
56
  when :requests
55
57
  "performance|*#{compile_requests_query}*|END|#{RailsPerformance::SCHEMA}"
58
+ when :resources
59
+ "resource|*#{compile_resource_query}*|END|#{RailsPerformance::SCHEMA}"
56
60
  when :sidekiq
57
61
  "sidekiq|*#{compile_sidekiq_query}*|END|#{RailsPerformance::SCHEMA}"
58
62
  when :delayed_job
@@ -89,6 +93,15 @@ module RailsPerformance
89
93
  str.join("*")
90
94
  end
91
95
 
96
+ def compile_resource_query
97
+ str = []
98
+ str << "server|#{q[:server]}|" if q[:server].present?
99
+ str << "context|#{q[:context]}|" if q[:context].present?
100
+ str << "role|#{q[:role]}|" if q[:role].present?
101
+ str << "datetime|#{q[:on].strftime("%Y%m%d")}*|" if q[:on].present?
102
+ str.join("*")
103
+ end
104
+
92
105
  def compile_delayed_job_query
93
106
  str = []
94
107
  str << "datetime|#{q[:on].strftime("%Y%m%d")}*|" if q[:on].present?
@@ -2,11 +2,23 @@ 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
6
 
6
7
  module RailsPerformance
7
8
  class Engine < ::Rails::Engine
8
9
  isolate_namespace RailsPerformance
9
10
 
11
+ initializer "rails_performance.resource_monitor" do
12
+ # check required gems are available
13
+ RailsPerformance._resource_monitor_enabled = !!(defined?(Sys::Filesystem) && defined?(Sys::CPU) && defined?(GetProcessMem))
14
+
15
+ next unless RailsPerformance.enabled
16
+ next if $rails_performance_running_mode == :console # rubocop:disable Style/GlobalVars
17
+
18
+ # start monitoring
19
+ RailsPerformance._resource_monitor = RailsPerformance::Extensions::ResourceMonitor.new("rails", "web")
20
+ end
21
+
10
22
  initializer "rails_performance.middleware" do |app|
11
23
  next unless RailsPerformance.enabled
12
24
 
@@ -24,10 +36,22 @@ module RailsPerformance
24
36
 
25
37
  if defined?(::Sidekiq)
26
38
  require_relative "gems/sidekiq_ext"
39
+
27
40
  Sidekiq.configure_server do |config|
28
41
  config.server_middleware do |chain|
29
42
  chain.add RailsPerformance::Gems::SidekiqExt
30
43
  end
44
+
45
+ config.on(:startup) do
46
+ if $rails_performance_running_mode != :console # rubocop:disable Style/GlobalVars
47
+ # stop web monitoring
48
+ # when we run sidekiq it also starts web monitoring (see above)
49
+ RailsPerformance._resource_monitor.stop_monitoring
50
+ RailsPerformance._resource_monitor = nil
51
+ # start background monitoring
52
+ RailsPerformance._resource_monitor = RailsPerformance::Extensions::ResourceMonitor.new("sidekiq", "background")
53
+ end
54
+ end
31
55
  end
32
56
  end
33
57
 
@@ -62,5 +86,9 @@ module RailsPerformance
62
86
  RailsPerformance::Gems::RakeExt.init
63
87
  end
64
88
  end
89
+
90
+ if defined?(::Rails::Console)
91
+ $rails_performance_running_mode = :console # rubocop:disable Style/GlobalVars
92
+ end
65
93
  end
66
94
  end
@@ -0,0 +1,103 @@
1
+ module RailsPerformance
2
+ module Extensions
3
+ class ResourceMonitor
4
+ attr_reader :context, :role
5
+
6
+ def initialize(context, role)
7
+ @context = context
8
+ @role = role
9
+ @mutex = Mutex.new
10
+ @thread = nil
11
+
12
+ return unless RailsPerformance._resource_monitor_enabled
13
+
14
+ start_monitoring
15
+ end
16
+
17
+ def start_monitoring
18
+ @mutex.synchronize do
19
+ return if @thread
20
+
21
+ # puts "Starting monitoring for #{context} - #{role}"
22
+ @thread = Thread.new do
23
+ loop do
24
+ run
25
+ rescue => e
26
+ ::Rails.logger.error "Monitor error: #{e.message}"
27
+ ensure
28
+ sleep 60
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ def stop_monitoring
35
+ @mutex.synchronize do
36
+ return unless @thread
37
+
38
+ # puts "Stopping monitoring"
39
+ @thread.kill
40
+ @thread = nil
41
+ end
42
+ end
43
+
44
+ def run
45
+ cpu = fetch_process_cpu_usage
46
+ memory = fetch_process_memory_usage
47
+ disk = fetch_disk_usage
48
+
49
+ store_data({cpu:, memory:, disk:})
50
+ end
51
+
52
+ def fetch_process_cpu_usage
53
+ load_averages = Sys::CPU.load_avg
54
+ {
55
+ one_min: load_averages[0],
56
+ five_min: load_averages[1],
57
+ fifteen_min: load_averages[2]
58
+ }
59
+ rescue => e
60
+ ::Rails.logger.error "Error fetching CPU usage: #{e.message}"
61
+ {one_min: 0.0, five_min: 0.0, fifteen_min: 0.0}
62
+ end
63
+
64
+ def fetch_process_memory_usage
65
+ GetProcessMem.new.bytes
66
+ rescue => e
67
+ ::Rails.logger.error "Error fetching memory usage: #{e.message}"
68
+ 0
69
+ end
70
+
71
+ def fetch_disk_usage(path = "/")
72
+ stat = Sys::Filesystem.stat(path)
73
+ {
74
+ available: stat.blocks_available * stat.block_size,
75
+ total: stat.blocks * stat.block_size,
76
+ used: (stat.blocks - stat.blocks_available) * stat.block_size
77
+ }
78
+ rescue => e
79
+ ::Rails.logger.error "Error fetching disk space: #{e.message}"
80
+ {available: 0, total: 0, used: 0}
81
+ end
82
+
83
+ def store_data(data)
84
+ ::Rails.logger.info("Server: #{server_id}, Context: #{context}, Role: #{role}, data: #{data}")
85
+
86
+ now = Time.current
87
+ now = now.change(sec: 0, usec: 0)
88
+ RailsPerformance::Models::ResourceRecord.new(
89
+ server: server_id,
90
+ context: context,
91
+ role: role,
92
+ datetime: now.strftime(RailsPerformance::FORMAT),
93
+ datetimei: now.to_i,
94
+ json: data
95
+ ).save
96
+ end
97
+
98
+ def server_id
99
+ @server_id ||= ENV["SERVER_ID"] || `hostname`.strip
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,47 @@
1
+ module RailsPerformance
2
+ module Models
3
+ class ResourceRecord < BaseRecord
4
+ attr_accessor :server, :context, :role, :datetime, :datetimei, :json
5
+
6
+ def initialize(server:, context:, role:, datetime:, datetimei:, json:)
7
+ @server = server
8
+ @context = context
9
+ @role = role
10
+ @datetime = datetime
11
+ @datetimei = datetimei
12
+ @json = json
13
+ end
14
+
15
+ def self.from_db(key, value)
16
+ items = key.split("|")
17
+
18
+ ResourceRecord.new(
19
+ server: items[2],
20
+ context: items[4],
21
+ role: items[6],
22
+ datetime: items[8],
23
+ datetimei: items[10],
24
+ json: value
25
+ )
26
+ end
27
+
28
+ def record_hash
29
+ {
30
+ server: server,
31
+ role: role,
32
+ context: context,
33
+ datetime: datetime,
34
+ datetimei: Time.at(datetimei.to_i),
35
+ cpu: value["cpu"],
36
+ memory: value["memory"],
37
+ disk: value["disk"]
38
+ }
39
+ end
40
+
41
+ def save
42
+ key = "resource|server|#{server}|context|#{context}|role|#{role}|datetime|#{datetime}|datetimei|#{datetimei}|END|#{RailsPerformance::SCHEMA}"
43
+ Utils.save_to_redis(key, json)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -32,6 +32,7 @@ module RailsPerformance
32
32
 
33
33
  def calculate_data
34
34
  now = Time.current
35
+ now = now.change(sec: 0, usec: 0)
35
36
  stop = Time.at(60 * (now.to_i / 60))
36
37
  offset = RailsPerformance::Reports::BaseReport.time_in_app_time_zone(now).utc_offset
37
38
  current = stop - RailsPerformance.duration
@@ -39,7 +40,6 @@ module RailsPerformance
39
40
  @data = []
40
41
  all = {}
41
42
 
42
- # read current values
43
43
  db.group_by(group).each do |(k, v)|
44
44
  yield(all, k, v)
45
45
  end
@@ -48,13 +48,46 @@ module RailsPerformance
48
48
  while current <= stop
49
49
  key = current.strftime(RailsPerformance::FORMAT)
50
50
  views = all[key].presence || 0
51
- @data << [(current.to_i + offset) * 1000, views.round(2)]
51
+ @data << [(current.to_i + offset) * 1000, views.is_a?(Numeric) ? views.round(2) : views]
52
52
  current += 1.minute
53
53
  end
54
54
 
55
55
  # sort by time
56
56
  @data.sort!
57
57
  end
58
+
59
+ # Generate series for our time range -> (now - duration)..now
60
+ # {
61
+ # 1732125540000 => 0,
62
+ # 1732125550000 => 0,
63
+ # ....
64
+ # }
65
+ def nil_data
66
+ @nil_data ||= begin
67
+ result = {}
68
+ now = Time.current
69
+ now = now.change(sec: 0, usec: 0)
70
+ stop = Time.at(60 * (now.to_i / 60))
71
+ offset = RailsPerformance::Reports::BaseReport.time_in_app_time_zone(now).utc_offset
72
+ current = stop - RailsPerformance.duration
73
+
74
+ while current <= stop
75
+ current.strftime(RailsPerformance::FORMAT)
76
+ result[(current.to_i + offset) * 1000] = nil
77
+ current += 1.minute
78
+ end
79
+
80
+ result
81
+ end
82
+ end
83
+
84
+ # {
85
+ # 1732125540000 => 1,
86
+ # 1732125550000 => 0,
87
+ # }
88
+ def nullify_data(input)
89
+ nil_data.merge(input).sort
90
+ end
58
91
  end
59
92
  end
60
93
  end
@@ -0,0 +1,44 @@
1
+ module RailsPerformance
2
+ module Reports
3
+ class ResourcesReport < BaseReport
4
+ def self.x
5
+ @datasource = RailsPerformance::DataSource.new(type: :resources)
6
+ db = @datasource.db
7
+ @data = RailsPerformance::Reports::ResourcesReport.new(db)
8
+ # RailsPerformance::Reports::ResourcesReport.x
9
+ end
10
+
11
+ def data
12
+ @data ||= db.data
13
+ .collect { |e| e.record_hash }
14
+ .group_by { |e| e[:server] + "///" + e[:context] + "///" + e[:role] }
15
+ .transform_values { |v| v.sort { |a, b| b[sort] <=> a[sort] } }
16
+ .transform_values { |v| v.map { |e| e.merge({datetimei: e[:datetimei].to_i}) } }
17
+ end
18
+
19
+ def cpu
20
+ @cpu ||= data.transform_values do |v|
21
+ nullify_data(v.each_with_object({}) do |e, res|
22
+ res[e[:datetimei] * 1000] = e[:cpu]["one_min"].to_f.round(2)
23
+ end)
24
+ end
25
+ end
26
+
27
+ def memory
28
+ @memory ||= data.transform_values do |v|
29
+ nullify_data(v.each_with_object({}) do |e, res|
30
+ res[e[:datetimei] * 1000] = e[:memory].to_f.round(2)
31
+ end)
32
+ end
33
+ end
34
+
35
+ def disk
36
+ @disk ||= data.transform_values do |v|
37
+ nullify_data(v.each_with_object({}) do |e, res|
38
+ res[e[:datetimei] * 1000] = e[:disk]["available"].to_f.round(2)
39
+ end)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,4 +1,4 @@
1
1
  module RailsPerformance
2
- VERSION = "1.3.3"
2
+ VERSION = "1.4.0.alpha1"
3
3
  SCHEMA = "1.0.1"
4
4
  end
@@ -11,6 +11,7 @@ require_relative "rails_performance/models/delayed_job_record"
11
11
  require_relative "rails_performance/models/grape_record"
12
12
  require_relative "rails_performance/models/trace_record"
13
13
  require_relative "rails_performance/models/rake_record"
14
+ require_relative "rails_performance/models/resource_record"
14
15
  require_relative "rails_performance/models/custom_record"
15
16
  require_relative "rails_performance/data_source"
16
17
  require_relative "rails_performance/utils"
@@ -24,6 +25,7 @@ require_relative "rails_performance/reports/slow_requests_report"
24
25
  require_relative "rails_performance/reports/breakdown_report"
25
26
  require_relative "rails_performance/reports/trace_report"
26
27
  require_relative "rails_performance/reports/percentile_report"
28
+ require_relative "rails_performance/reports/resources_report"
27
29
  require_relative "rails_performance/extensions/trace"
28
30
  require_relative "rails_performance/thread/current_request"
29
31
 
@@ -117,6 +119,17 @@ module RailsPerformance
117
119
  mattr_accessor :ignore_trace_headers
118
120
  @@ignore_trace_headers = ["datetimei"]
119
121
 
122
+ mattr_accessor :_resource_monitor
123
+ @@_resource_monitor = nil
124
+
125
+ # to check if we are running in console mode
126
+ mattr_accessor :_running_mode
127
+ @@_running_mode = nil
128
+
129
+ # by default we don't want to monitor resources, but we can enable it by adding required gems
130
+ mattr_accessor :_resource_monitor_enabled
131
+ @@_resource_monitor_enabled = false
132
+
120
133
  def self.setup
121
134
  yield(self)
122
135
  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: 1.3.3
4
+ version: 1.4.0.alpha1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Igor Kasyanchuk
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-09 00:00:00.000000000 Z
11
+ date: 2024-11-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -248,6 +248,48 @@ dependencies:
248
248
  - - ">="
249
249
  - !ruby/object:Gem::Version
250
250
  version: '0'
251
+ - !ruby/object:Gem::Dependency
252
+ name: sys-filesystem
253
+ requirement: !ruby/object:Gem::Requirement
254
+ requirements:
255
+ - - ">="
256
+ - !ruby/object:Gem::Version
257
+ version: '0'
258
+ type: :development
259
+ prerelease: false
260
+ version_requirements: !ruby/object:Gem::Requirement
261
+ requirements:
262
+ - - ">="
263
+ - !ruby/object:Gem::Version
264
+ version: '0'
265
+ - !ruby/object:Gem::Dependency
266
+ name: sys-cpu
267
+ requirement: !ruby/object:Gem::Requirement
268
+ requirements:
269
+ - - ">="
270
+ - !ruby/object:Gem::Version
271
+ version: '0'
272
+ type: :development
273
+ prerelease: false
274
+ version_requirements: !ruby/object:Gem::Requirement
275
+ requirements:
276
+ - - ">="
277
+ - !ruby/object:Gem::Version
278
+ version: '0'
279
+ - !ruby/object:Gem::Dependency
280
+ name: get_process_mem
281
+ requirement: !ruby/object:Gem::Requirement
282
+ requirements:
283
+ - - ">="
284
+ - !ruby/object:Gem::Version
285
+ version: '0'
286
+ type: :development
287
+ prerelease: false
288
+ version_requirements: !ruby/object:Gem::Requirement
289
+ requirements:
290
+ - - ">="
291
+ - !ruby/object:Gem::Version
292
+ version: '0'
251
293
  description: 3rd party dependency-free solution how to monitor performance of your
252
294
  Rails applications.
253
295
  email:
@@ -299,6 +341,7 @@ files:
299
341
  - app/views/rails_performance/rails_performance/recent.html.erb
300
342
  - app/views/rails_performance/rails_performance/recent.js.erb
301
343
  - app/views/rails_performance/rails_performance/requests.html.erb
344
+ - app/views/rails_performance/rails_performance/resources.html.erb
302
345
  - app/views/rails_performance/rails_performance/sidekiq.html.erb
303
346
  - app/views/rails_performance/rails_performance/slow.html.erb
304
347
  - app/views/rails_performance/rails_performance/summary.js.erb
@@ -316,6 +359,7 @@ files:
316
359
  - lib/rails_performance.rb
317
360
  - lib/rails_performance/data_source.rb
318
361
  - lib/rails_performance/engine.rb
362
+ - lib/rails_performance/extensions/resources_monitor.rb
319
363
  - lib/rails_performance/extensions/trace.rb
320
364
  - lib/rails_performance/gems/custom_ext.rb
321
365
  - lib/rails_performance/gems/delayed_job_ext.rb
@@ -330,6 +374,7 @@ files:
330
374
  - lib/rails_performance/models/grape_record.rb
331
375
  - lib/rails_performance/models/rake_record.rb
332
376
  - lib/rails_performance/models/request_record.rb
377
+ - lib/rails_performance/models/resource_record.rb
333
378
  - lib/rails_performance/models/sidekiq_record.rb
334
379
  - lib/rails_performance/models/trace_record.rb
335
380
  - lib/rails_performance/rails/middleware.rb
@@ -340,6 +385,7 @@ files:
340
385
  - lib/rails_performance/reports/percentile_report.rb
341
386
  - lib/rails_performance/reports/recent_requests_report.rb
342
387
  - lib/rails_performance/reports/requests_report.rb
388
+ - lib/rails_performance/reports/resources_report.rb
343
389
  - lib/rails_performance/reports/response_time_report.rb
344
390
  - lib/rails_performance/reports/slow_requests_report.rb
345
391
  - lib/rails_performance/reports/throughput_report.rb
@@ -366,7 +412,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
366
412
  - !ruby/object:Gem::Version
367
413
  version: '0'
368
414
  requirements: []
369
- rubygems_version: 3.3.7
415
+ rubygems_version: 3.5.22
370
416
  signing_key:
371
417
  specification_version: 4
372
418
  summary: Simple Rails Performance tracker. Alternative to the NewRelic, Datadog or