railswatch 1.0.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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +485 -0
- data/Rakefile +37 -0
- data/app/assets/config/railswatch_manifest.js +0 -0
- data/app/assets/images/activity.svg +13 -0
- data/app/assets/images/bot.svg +1 -0
- data/app/assets/images/close.svg +13 -0
- data/app/assets/images/details.svg +3 -0
- data/app/assets/images/download.svg +3 -0
- data/app/assets/images/export.svg +13 -0
- data/app/assets/images/external.svg +1 -0
- data/app/assets/images/git.svg +1 -0
- data/app/assets/images/github.svg +1 -0
- data/app/assets/images/home.svg +16 -0
- data/app/assets/images/import.svg +13 -0
- data/app/assets/images/menu.svg +16 -0
- data/app/assets/images/moon.svg +3 -0
- data/app/assets/images/stat.svg +1 -0
- data/app/assets/images/sun.svg +4 -0
- data/app/assets/images/user.svg +1 -0
- data/app/controllers/railswatch/base_controller.rb +35 -0
- data/app/controllers/railswatch/concerns/csv_exportable.rb +31 -0
- data/app/controllers/railswatch/railswatch_controller.rb +183 -0
- data/app/engine_assets/javascripts/apex_ext.js +30 -0
- data/app/engine_assets/javascripts/application.js +9 -0
- data/app/engine_assets/javascripts/autoupdate.js +79 -0
- data/app/engine_assets/javascripts/charts.js +279 -0
- data/app/engine_assets/javascripts/navbar.js +11 -0
- data/app/engine_assets/javascripts/panel.js +43 -0
- data/app/engine_assets/javascripts/table.js +12 -0
- data/app/engine_assets/javascripts/theme.js +43 -0
- data/app/engine_assets/stylesheets/panel.css +111 -0
- data/app/engine_assets/stylesheets/responsive.css +102 -0
- data/app/engine_assets/stylesheets/style.css +960 -0
- data/app/helpers/railswatch/railswatch_helper.rb +338 -0
- data/app/views/railswatch/_panel.html.erb +15 -0
- data/app/views/railswatch/layouts/railswatch.html.erb +81 -0
- data/app/views/railswatch/railswatch/_card.html.erb +7 -0
- data/app/views/railswatch/railswatch/_chart.html.erb +13 -0
- data/app/views/railswatch/railswatch/_crashes_table_content.html.erb +62 -0
- data/app/views/railswatch/railswatch/_custom_events_table_content.html.erb +27 -0
- data/app/views/railswatch/railswatch/_delayed_job_table_content.html.erb +52 -0
- data/app/views/railswatch/railswatch/_export.html.erb +4 -0
- data/app/views/railswatch/railswatch/_grape_requests_table_content.html.erb +31 -0
- data/app/views/railswatch/railswatch/_overview.html.erb +124 -0
- data/app/views/railswatch/railswatch/_rake_tasks_table_content.html.erb +25 -0
- data/app/views/railswatch/railswatch/_recent_requests_table_content.html.erb +28 -0
- data/app/views/railswatch/railswatch/_recent_row.html.erb +41 -0
- data/app/views/railswatch/railswatch/_requests_table_content.html.erb +51 -0
- data/app/views/railswatch/railswatch/_sidekiq_jobs_table_content.html.erb +50 -0
- data/app/views/railswatch/railswatch/_summary.html.erb +50 -0
- data/app/views/railswatch/railswatch/_table.html.erb +30 -0
- data/app/views/railswatch/railswatch/_trace.html.erb +78 -0
- data/app/views/railswatch/railswatch/crashes.html.erb +2 -0
- data/app/views/railswatch/railswatch/custom.html.erb +6 -0
- data/app/views/railswatch/railswatch/delayed_job.html.erb +6 -0
- data/app/views/railswatch/railswatch/grape.html.erb +6 -0
- data/app/views/railswatch/railswatch/index.html.erb +9 -0
- data/app/views/railswatch/railswatch/rake.html.erb +6 -0
- data/app/views/railswatch/railswatch/recent.html.erb +2 -0
- data/app/views/railswatch/railswatch/requests.html.erb +2 -0
- data/app/views/railswatch/railswatch/resources.html.erb +28 -0
- data/app/views/railswatch/railswatch/sidekiq.html.erb +6 -0
- data/app/views/railswatch/railswatch/slow.html.erb +2 -0
- data/app/views/railswatch/railswatch/summary.js.erb +3 -0
- data/app/views/railswatch/railswatch/trace.js.erb +9 -0
- data/app/views/railswatch/shared/_header.html.erb +39 -0
- data/app/views/railswatch/shared/_page_header.html.erb +23 -0
- data/config/routes.rb +27 -0
- data/lib/generators/railswatch/install/USAGE +19 -0
- data/lib/generators/railswatch/install/install_generator.rb +46 -0
- data/lib/generators/railswatch/install/templates/create_railswatch_tables.rb +140 -0
- data/lib/generators/railswatch/install/templates/initializer.rb +87 -0
- data/lib/railswatch/data_source.rb +106 -0
- data/lib/railswatch/engine.rb +103 -0
- data/lib/railswatch/events/record.rb +63 -0
- data/lib/railswatch/extensions/trace.rb +14 -0
- data/lib/railswatch/extensions/trace_db.rb +21 -0
- data/lib/railswatch/gems/custom_ext.rb +38 -0
- data/lib/railswatch/gems/delayed_job_ext.rb +70 -0
- data/lib/railswatch/gems/grape_ext.rb +64 -0
- data/lib/railswatch/gems/rake_ext.rb +69 -0
- data/lib/railswatch/gems/sidekiq_ext.rb +55 -0
- data/lib/railswatch/instrument/metrics_collector.rb +70 -0
- data/lib/railswatch/interface.rb +9 -0
- data/lib/railswatch/models/application_record.rb +31 -0
- data/lib/railswatch/models/base_record.rb +59 -0
- data/lib/railswatch/models/collection.rb +37 -0
- data/lib/railswatch/models/custom_record.rb +32 -0
- data/lib/railswatch/models/delayed_job_record.rb +39 -0
- data/lib/railswatch/models/event_record.rb +11 -0
- data/lib/railswatch/models/grape_record.rb +61 -0
- data/lib/railswatch/models/rake_record.rb +33 -0
- data/lib/railswatch/models/request_record.rb +105 -0
- data/lib/railswatch/models/resource_record.rb +33 -0
- data/lib/railswatch/models/sidekiq_record.rb +41 -0
- data/lib/railswatch/models/trace_record.rb +21 -0
- data/lib/railswatch/pruner.rb +47 -0
- data/lib/railswatch/rails/middleware.rb +117 -0
- data/lib/railswatch/rails/query_builder.rb +20 -0
- data/lib/railswatch/reports/annotations_report.rb +13 -0
- data/lib/railswatch/reports/base_report.rb +48 -0
- data/lib/railswatch/reports/breakdown_report.rb +11 -0
- data/lib/railswatch/reports/crash_report.rb +11 -0
- data/lib/railswatch/reports/overview_report.rb +88 -0
- data/lib/railswatch/reports/percentile_report.rb +16 -0
- data/lib/railswatch/reports/recent_requests_report.rb +21 -0
- data/lib/railswatch/reports/requests_report.rb +75 -0
- data/lib/railswatch/reports/resources_report.rb +42 -0
- data/lib/railswatch/reports/response_time_report.rb +27 -0
- data/lib/railswatch/reports/slow_requests_report.rb +21 -0
- data/lib/railswatch/reports/throughput_report.rb +16 -0
- data/lib/railswatch/reports/trace_report.rb +17 -0
- data/lib/railswatch/system_monitor/resources_monitor.rb +88 -0
- data/lib/railswatch/thread/current_request.rb +37 -0
- data/lib/railswatch/utils.rb +58 -0
- data/lib/railswatch/version.rb +7 -0
- data/lib/railswatch/widgets/base.rb +17 -0
- data/lib/railswatch/widgets/card.rb +19 -0
- data/lib/railswatch/widgets/chart.rb +33 -0
- data/lib/railswatch/widgets/crashes_table.rb +27 -0
- data/lib/railswatch/widgets/custom_events_table.rb +48 -0
- data/lib/railswatch/widgets/delayed_job_table.rb +31 -0
- data/lib/railswatch/widgets/grape_requests_table.rb +31 -0
- data/lib/railswatch/widgets/percentile_card.rb +23 -0
- data/lib/railswatch/widgets/rake_tasks_table.rb +31 -0
- data/lib/railswatch/widgets/recent_requests_table.rb +35 -0
- data/lib/railswatch/widgets/requests_table.rb +27 -0
- data/lib/railswatch/widgets/resource_chart.rb +116 -0
- data/lib/railswatch/widgets/response_time_chart.rb +29 -0
- data/lib/railswatch/widgets/sidekiq_jobs_table.rb +31 -0
- data/lib/railswatch/widgets/slow_requests_table.rb +33 -0
- data/lib/railswatch/widgets/table.rb +43 -0
- data/lib/railswatch/widgets/throughput_chart.rb +29 -0
- data/lib/railswatch.rb +184 -0
- data/lib/tasks/railswatch.rake +9 -0
- metadata +445 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<header class="rm-page-header">
|
|
2
|
+
<div class="rm-page-header__copy">
|
|
3
|
+
<p class="rm-page-header__eyebrow"><%= eyebrow %></p>
|
|
4
|
+
<div class="rm-page-header__title-row">
|
|
5
|
+
<h1 class="rm-page-header__title"><%= title %></h1>
|
|
6
|
+
<% if local_assigns[:badge].present? %>
|
|
7
|
+
<span class="rm-badge"><%= badge %></span>
|
|
8
|
+
<% end %>
|
|
9
|
+
</div>
|
|
10
|
+
<p class="rm-page-header__description"><%= description %></p>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="rm-page-header__actions">
|
|
14
|
+
<% if local_assigns[:autoupdate_interval].present? %>
|
|
15
|
+
<railswatch-autoupdate interval="<%= autoupdate_interval %>">
|
|
16
|
+
<label class="rm-toggle" id="autoupdate_label">
|
|
17
|
+
<input type="checkbox" />
|
|
18
|
+
<span>Auto-update</span>
|
|
19
|
+
</label>
|
|
20
|
+
</railswatch-autoupdate>
|
|
21
|
+
<% end %>
|
|
22
|
+
</div>
|
|
23
|
+
</header>
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Railswatch::Engine.routes.draw do
|
|
4
|
+
get '/' => 'railswatch#index', :as => :railswatch
|
|
5
|
+
|
|
6
|
+
get '/requests' => 'railswatch#requests', :as => :railswatch_requests
|
|
7
|
+
get '/crashes' => 'railswatch#crashes', :as => :railswatch_crashes
|
|
8
|
+
get '/recent' => 'railswatch#recent', :as => :railswatch_recent
|
|
9
|
+
get '/slow' => 'railswatch#slow', :as => :railswatch_slow
|
|
10
|
+
|
|
11
|
+
get '/trace/:id' => 'railswatch#trace', :as => :railswatch_trace
|
|
12
|
+
get '/summary' => 'railswatch#summary', :as => :railswatch_summary
|
|
13
|
+
|
|
14
|
+
get '/sidekiq' => 'railswatch#sidekiq', :as => :railswatch_sidekiq
|
|
15
|
+
get '/delayed_job' => 'railswatch#delayed_job', :as => :railswatch_delayed_job
|
|
16
|
+
get '/grape' => 'railswatch#grape', :as => :railswatch_grape
|
|
17
|
+
get '/rake' => 'railswatch#rake', :as => :railswatch_rake
|
|
18
|
+
get '/custom' => 'railswatch#custom', :as => :railswatch_custom
|
|
19
|
+
get '/resources' => 'railswatch#resources', :as => :railswatch_resources
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
Rails.application.routes.draw do
|
|
23
|
+
mount Railswatch::Engine => Railswatch.mount_at, :as => 'railswatch'
|
|
24
|
+
rescue ArgumentError
|
|
25
|
+
# already added
|
|
26
|
+
# this code exist here because engine not includes routing automatically
|
|
27
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Description:
|
|
2
|
+
Generates initial config and database migration for railswatch
|
|
3
|
+
|
|
4
|
+
Example:
|
|
5
|
+
bin/rails generate railswatch:install
|
|
6
|
+
|
|
7
|
+
This will create:
|
|
8
|
+
config/initializers/railswatch.rb
|
|
9
|
+
db/migrate/TIMESTAMP_create_railswatch_tables.rb
|
|
10
|
+
|
|
11
|
+
Primary database setup:
|
|
12
|
+
keep `config.database_connection_name = nil`
|
|
13
|
+
bin/rails db:migrate
|
|
14
|
+
|
|
15
|
+
Separate database setup:
|
|
16
|
+
add `railswatch` to config/database.yml
|
|
17
|
+
set `config.database_connection_name = :railswatch`
|
|
18
|
+
bin/rails db:migrate
|
|
19
|
+
bin/rails db:migrate:railswatch
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators/migration'
|
|
4
|
+
|
|
5
|
+
module Railswatch
|
|
6
|
+
class InstallGenerator < ::Rails::Generators::Base
|
|
7
|
+
include ::Rails::Generators::Migration
|
|
8
|
+
|
|
9
|
+
source_root File.expand_path('templates', __dir__)
|
|
10
|
+
desc 'Generates initial config for railswatch gem'
|
|
11
|
+
|
|
12
|
+
def copy_initializer_file
|
|
13
|
+
copy_file 'initializer.rb', 'config/initializers/railswatch.rb'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def install_migration
|
|
17
|
+
migration_template(
|
|
18
|
+
'create_railswatch_tables.rb',
|
|
19
|
+
'db/migrate/create_railswatch_tables.rb'
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def show_database_setup # rubocop:disable Metrics/MethodLength
|
|
24
|
+
say <<~TEXT
|
|
25
|
+
|
|
26
|
+
railswatch was configured with database-backed storage.
|
|
27
|
+
|
|
28
|
+
Primary database:
|
|
29
|
+
Leave `config.database_connection_name = nil`.
|
|
30
|
+
Run `bin/rails db:migrate`.
|
|
31
|
+
|
|
32
|
+
Separate database:
|
|
33
|
+
1. Add a `railswatch` database entry to `config/database.yml`
|
|
34
|
+
2. Set `config.database_connection_name = :railswatch`
|
|
35
|
+
3. Run `bin/rails db:migrate` for your app database
|
|
36
|
+
4. Run `bin/rails db:migrate:railswatch`
|
|
37
|
+
|
|
38
|
+
You can prune old data later with `bin/rails railswatch:prune`.
|
|
39
|
+
TEXT
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.next_migration_number(_dirname)
|
|
43
|
+
Time.now.utc.strftime('%Y%m%d%H%M%S')
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateRailswatchTables < ActiveRecord::Migration[7.0]
|
|
4
|
+
def change # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
5
|
+
create_table :railswatch_request_records do |t|
|
|
6
|
+
t.string :controller
|
|
7
|
+
t.string :action
|
|
8
|
+
t.string :format
|
|
9
|
+
t.string :status
|
|
10
|
+
t.string :http_method
|
|
11
|
+
t.string :path
|
|
12
|
+
t.string :request_id, null: false
|
|
13
|
+
t.float :duration_ms
|
|
14
|
+
t.float :view_runtime_ms
|
|
15
|
+
t.float :db_runtime_ms
|
|
16
|
+
t.string :http_referer
|
|
17
|
+
t.text :custom_data
|
|
18
|
+
t.text :exception
|
|
19
|
+
t.text :backtrace
|
|
20
|
+
t.text :request_context
|
|
21
|
+
t.datetime :occurred_at, null: false
|
|
22
|
+
t.timestamps
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
add_index :railswatch_request_records, :occurred_at
|
|
26
|
+
add_index :railswatch_request_records, :request_id, unique: true
|
|
27
|
+
add_index :railswatch_request_records, %i[status occurred_at],
|
|
28
|
+
name: 'idx_rp_requests_on_status_and_occurred_at'
|
|
29
|
+
add_index :railswatch_request_records, %i[controller action format occurred_at],
|
|
30
|
+
name: 'idx_rp_requests_on_grouping_fields'
|
|
31
|
+
|
|
32
|
+
create_table :railswatch_trace_records do |t|
|
|
33
|
+
t.string :request_id, null: false
|
|
34
|
+
t.text :entries
|
|
35
|
+
t.datetime :occurred_at, null: false
|
|
36
|
+
t.timestamps
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
add_index :railswatch_trace_records, :request_id, unique: true
|
|
40
|
+
add_index :railswatch_trace_records, :occurred_at
|
|
41
|
+
|
|
42
|
+
create_table :railswatch_sidekiq_records do |t|
|
|
43
|
+
t.string :queue
|
|
44
|
+
t.string :worker
|
|
45
|
+
t.string :jid, null: false
|
|
46
|
+
t.integer :enqueued_ati
|
|
47
|
+
t.integer :start_timei
|
|
48
|
+
t.string :status
|
|
49
|
+
t.float :duration_ms
|
|
50
|
+
t.text :message
|
|
51
|
+
t.text :job_args
|
|
52
|
+
t.text :error_backtrace
|
|
53
|
+
t.datetime :occurred_at, null: false
|
|
54
|
+
t.timestamps
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
add_index :railswatch_sidekiq_records, :occurred_at
|
|
58
|
+
add_index :railswatch_sidekiq_records, %i[queue worker occurred_at],
|
|
59
|
+
name: 'idx_rp_sidekiq_on_queue_worker_time'
|
|
60
|
+
add_index :railswatch_sidekiq_records, :jid
|
|
61
|
+
|
|
62
|
+
create_table :railswatch_delayed_job_records do |t|
|
|
63
|
+
t.string :jid
|
|
64
|
+
t.string :source_type
|
|
65
|
+
t.string :class_name
|
|
66
|
+
t.string :method_name
|
|
67
|
+
t.string :status
|
|
68
|
+
t.float :duration_ms
|
|
69
|
+
t.text :job_args
|
|
70
|
+
t.text :error_message
|
|
71
|
+
t.text :error_backtrace
|
|
72
|
+
t.datetime :occurred_at, null: false
|
|
73
|
+
t.timestamps
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
add_index :railswatch_delayed_job_records, :occurred_at
|
|
77
|
+
add_index :railswatch_delayed_job_records, %i[status occurred_at], name: 'idx_rp_delayed_jobs_on_status_time'
|
|
78
|
+
|
|
79
|
+
create_table :railswatch_grape_records do |t|
|
|
80
|
+
t.string :format
|
|
81
|
+
t.string :path
|
|
82
|
+
t.string :status
|
|
83
|
+
t.string :http_method
|
|
84
|
+
t.string :request_id
|
|
85
|
+
t.float :endpoint_render_grape_ms
|
|
86
|
+
t.float :endpoint_run_grape_ms
|
|
87
|
+
t.float :format_response_grape_ms
|
|
88
|
+
t.datetime :occurred_at, null: false
|
|
89
|
+
t.timestamps
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
add_index :railswatch_grape_records, :occurred_at
|
|
93
|
+
add_index :railswatch_grape_records, %i[status occurred_at], name: 'idx_rp_grape_on_status_time'
|
|
94
|
+
|
|
95
|
+
create_table :railswatch_rake_records do |t|
|
|
96
|
+
t.text :task
|
|
97
|
+
t.string :status
|
|
98
|
+
t.float :duration_ms
|
|
99
|
+
t.datetime :occurred_at, null: false
|
|
100
|
+
t.timestamps
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
add_index :railswatch_rake_records, :occurred_at
|
|
104
|
+
add_index :railswatch_rake_records, %i[status occurred_at], name: 'idx_rp_rake_on_status_time'
|
|
105
|
+
|
|
106
|
+
create_table :railswatch_custom_records do |t|
|
|
107
|
+
t.string :tag_name
|
|
108
|
+
t.string :namespace_name
|
|
109
|
+
t.string :status
|
|
110
|
+
t.float :duration_ms
|
|
111
|
+
t.datetime :occurred_at, null: false
|
|
112
|
+
t.timestamps
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
add_index :railswatch_custom_records, :occurred_at
|
|
116
|
+
add_index :railswatch_custom_records, %i[status occurred_at], name: 'idx_rp_custom_on_status_time'
|
|
117
|
+
|
|
118
|
+
create_table :railswatch_resource_records do |t|
|
|
119
|
+
t.string :server
|
|
120
|
+
t.string :context
|
|
121
|
+
t.string :role
|
|
122
|
+
t.text :payload
|
|
123
|
+
t.datetime :occurred_at, null: false
|
|
124
|
+
t.timestamps
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
add_index :railswatch_resource_records, :occurred_at
|
|
128
|
+
add_index :railswatch_resource_records, %i[server context role occurred_at],
|
|
129
|
+
name: 'idx_rp_resources_on_server_context_role_time'
|
|
130
|
+
|
|
131
|
+
create_table :railswatch_event_records do |t|
|
|
132
|
+
t.string :name
|
|
133
|
+
t.text :options
|
|
134
|
+
t.datetime :occurred_at, null: false
|
|
135
|
+
t.timestamps
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
add_index :railswatch_event_records, :occurred_at
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
if defined?(Railswatch)
|
|
4
|
+
Railswatch.setup do |config|
|
|
5
|
+
# Use the primary application database by default.
|
|
6
|
+
# To use a dedicated database, define an additional database config and set:
|
|
7
|
+
# config.database_connection_name = :railswatch
|
|
8
|
+
config.database_connection_name = nil
|
|
9
|
+
|
|
10
|
+
# All data we collect
|
|
11
|
+
config.duration = 4.hours
|
|
12
|
+
|
|
13
|
+
# Recent Requests configuration
|
|
14
|
+
config.recent_requests_time_window = 60.minutes
|
|
15
|
+
# config.recent_requests_limit = nil # number of recent requests
|
|
16
|
+
|
|
17
|
+
# Slow Requests configuration
|
|
18
|
+
config.slow_requests_time_window = 4.hours
|
|
19
|
+
# config.slow_requests_limit = 500 # number of slow requests
|
|
20
|
+
config.slow_requests_threshold = 500 # ms
|
|
21
|
+
|
|
22
|
+
config.retention = {
|
|
23
|
+
requests: config.duration,
|
|
24
|
+
sidekiq: config.duration,
|
|
25
|
+
delayed_job: config.duration,
|
|
26
|
+
grape: config.duration,
|
|
27
|
+
rake: config.duration,
|
|
28
|
+
custom: config.duration,
|
|
29
|
+
traces: config.recent_requests_time_window,
|
|
30
|
+
resources: 24.hours,
|
|
31
|
+
events: nil
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
config.debug = false
|
|
35
|
+
config.enabled = true
|
|
36
|
+
|
|
37
|
+
# default path where to mount gem
|
|
38
|
+
config.mount_at = '/railswatch'
|
|
39
|
+
|
|
40
|
+
# protect your Performance Dashboard with HTTP BASIC password
|
|
41
|
+
config.http_basic_authentication_enabled = false
|
|
42
|
+
config.http_basic_authentication_user_name = 'railswatch'
|
|
43
|
+
config.http_basic_authentication_password = 'password12'
|
|
44
|
+
|
|
45
|
+
# if you need an additional rules to check user permissions
|
|
46
|
+
config.verify_access_proc = proc { |_controller| true }
|
|
47
|
+
# for example when you have `current_user`
|
|
48
|
+
# config.verify_access_proc = proc { |controller| controller.current_user && controller.current_user.admin? }
|
|
49
|
+
|
|
50
|
+
# Override engine url options, necessary if hosting under a unique domain
|
|
51
|
+
# config.url_options = {host: "sub.example.com"}
|
|
52
|
+
|
|
53
|
+
# You can ignore endpoints with Rails standard notation controller#action
|
|
54
|
+
# config.ignored_endpoints = ['HomeController#contact']
|
|
55
|
+
|
|
56
|
+
# You can ignore request paths by specifying the beginning of the path.
|
|
57
|
+
# For example, all routes starting with '/admin' can be ignored:
|
|
58
|
+
config.ignored_paths = ['/railswatch']
|
|
59
|
+
|
|
60
|
+
# store custom data for the request
|
|
61
|
+
# config.custom_data_proc = proc do |env|
|
|
62
|
+
# request = Rack::Request.new(env)
|
|
63
|
+
# {
|
|
64
|
+
# email: request.env['warden'].user&.email, # if you are using Devise for example
|
|
65
|
+
# user_agent: request.env['HTTP_USER_AGENT']
|
|
66
|
+
# }
|
|
67
|
+
# end
|
|
68
|
+
|
|
69
|
+
# Attach current user info to each request context (stored filtered alongside IP/params).
|
|
70
|
+
# The proc receives the raw Rack env and should return a plain hash.
|
|
71
|
+
# Sensitive values are filtered automatically — keep this lightweight (IDs, roles, emails).
|
|
72
|
+
# config.current_user_proc = proc do |env|
|
|
73
|
+
# user = env['warden']&.user # Devise / Warden
|
|
74
|
+
# { id: user&.id, email: user&.email } if user
|
|
75
|
+
# end
|
|
76
|
+
|
|
77
|
+
# config home button link
|
|
78
|
+
config.home_link = '/'
|
|
79
|
+
config.skipable_rake_tasks = ['webpacker:compile']
|
|
80
|
+
config.include_rake_tasks = false
|
|
81
|
+
config.include_custom_events = true
|
|
82
|
+
|
|
83
|
+
# If enabled, the system monitor will be displayed on the dashboard
|
|
84
|
+
# to enabled add required gems (see README)
|
|
85
|
+
# config.system_monitor_duration = 24.hours
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
|
|
5
|
+
module Railswatch
|
|
6
|
+
class DataSource
|
|
7
|
+
class RelationWrapper
|
|
8
|
+
extend Forwardable
|
|
9
|
+
|
|
10
|
+
def_delegators :@relation,
|
|
11
|
+
:where, :order, :limit, :pluck, :map, :group, :count,
|
|
12
|
+
:average, :maximum, :all, :to_a, :find_by, :each,
|
|
13
|
+
:klass
|
|
14
|
+
|
|
15
|
+
def initialize(relation)
|
|
16
|
+
@relation = relation
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def data
|
|
20
|
+
@relation.to_a
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
KLASSES = {
|
|
25
|
+
requests: Railswatch::Models::RequestRecord,
|
|
26
|
+
sidekiq: Railswatch::Models::SidekiqRecord,
|
|
27
|
+
delayed_job: Railswatch::Models::DelayedJobRecord,
|
|
28
|
+
grape: Railswatch::Models::GrapeRecord,
|
|
29
|
+
rake: Railswatch::Models::RakeRecord,
|
|
30
|
+
custom: Railswatch::Models::CustomRecord,
|
|
31
|
+
resources: Railswatch::Models::ResourceRecord
|
|
32
|
+
}.freeze
|
|
33
|
+
|
|
34
|
+
attr_reader :query, :klass, :type, :days
|
|
35
|
+
|
|
36
|
+
def initialize(type:, query: {}, days: Railswatch::Utils.days(Railswatch.duration))
|
|
37
|
+
@type = type
|
|
38
|
+
@klass = KLASSES.fetch(type)
|
|
39
|
+
@query = query
|
|
40
|
+
@days = days
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def db
|
|
44
|
+
scope = klass.all
|
|
45
|
+
scope = apply_time_window(scope)
|
|
46
|
+
RelationWrapper.new(apply_filters(scope))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def default?
|
|
50
|
+
query.empty?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def apply_time_window(scope)
|
|
56
|
+
return scope.where(occurred_at: query[:on].all_day) if query[:on].present?
|
|
57
|
+
|
|
58
|
+
now = Railswatch::Utils.kind_of_now
|
|
59
|
+
scope.where(occurred_at: (now - days.days)..now)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def apply_filters(scope) # rubocop:disable Metrics/MethodLength
|
|
63
|
+
case type
|
|
64
|
+
when :requests
|
|
65
|
+
apply_request_filters(scope)
|
|
66
|
+
when :resources
|
|
67
|
+
apply_resource_filters(scope)
|
|
68
|
+
when :sidekiq
|
|
69
|
+
apply_sidekiq_filters(scope)
|
|
70
|
+
when :delayed_job, :grape, :rake, :custom
|
|
71
|
+
apply_status_filter(scope)
|
|
72
|
+
else
|
|
73
|
+
scope
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def apply_request_filters(scope) # rubocop:disable Metrics/AbcSize
|
|
78
|
+
scope = scope.where(controller: query[:controller]) if query[:controller].present?
|
|
79
|
+
scope = scope.where(action: query[:action]) if query[:action].present?
|
|
80
|
+
scope = scope.where(format: query[:format]) if query[:format].present?
|
|
81
|
+
scope = scope.where(status: query[:status].to_s) if query[:status].present?
|
|
82
|
+
scope = scope.where(http_method: query[:method]) if query[:method].present?
|
|
83
|
+
scope = scope.where(path: query[:path]) if query[:path].present?
|
|
84
|
+
scope
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def apply_resource_filters(scope) # rubocop:disable Metrics/AbcSize
|
|
88
|
+
scope = scope.where(server: query[:server]) if query[:server].present?
|
|
89
|
+
scope = scope.where(context: query[:context]) if query[:context].present?
|
|
90
|
+
scope = scope.where(role: query[:role]) if query[:role].present?
|
|
91
|
+
scope
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def apply_sidekiq_filters(scope) # rubocop:disable Metrics/AbcSize
|
|
95
|
+
scope = scope.where(queue: query[:queue]) if query[:queue].present?
|
|
96
|
+
scope = scope.where(worker: query[:worker]) if query[:worker].present?
|
|
97
|
+
scope = scope.where(status: query[:status].to_s) if query[:status].present?
|
|
98
|
+
scope
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def apply_status_filter(scope)
|
|
102
|
+
scope = scope.where(status: query[:status].to_s) if query[:status].present?
|
|
103
|
+
scope
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'action_view/log_subscriber'
|
|
4
|
+
require_relative 'rails/middleware'
|
|
5
|
+
require_relative 'models/collection'
|
|
6
|
+
require_relative 'instrument/metrics_collector'
|
|
7
|
+
require_relative 'system_monitor/resources_monitor'
|
|
8
|
+
|
|
9
|
+
module Railswatch
|
|
10
|
+
class Engine < ::Rails::Engine
|
|
11
|
+
isolate_namespace Railswatch
|
|
12
|
+
|
|
13
|
+
isolate_assets assets_subdir: 'engine_assets'
|
|
14
|
+
|
|
15
|
+
initializer 'railswatch.resource_monitor' do
|
|
16
|
+
# check required gems are available
|
|
17
|
+
Railswatch._resource_monitor_enabled =
|
|
18
|
+
!(defined?(Sys::Filesystem) && defined?(Sys::CPU) && defined?(GetProcessMem)).nil?
|
|
19
|
+
|
|
20
|
+
next unless Railswatch.enabled
|
|
21
|
+
next if ::Rails.env.test?
|
|
22
|
+
next if $railswatch_running_mode == :console # rubocop:disable Style/GlobalVars
|
|
23
|
+
|
|
24
|
+
# start monitoring
|
|
25
|
+
Railswatch._resource_monitor = Railswatch::SystemMonitor::ResourcesMonitor.new(
|
|
26
|
+
ENV['RAILSWATCH_SERVER_CONTEXT'].presence || 'rails',
|
|
27
|
+
ENV['RAILSWATCH_SERVER_ROLE'].presence || 'web'
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
initializer 'railswatch.middleware' do |app|
|
|
32
|
+
next unless Railswatch.enabled
|
|
33
|
+
|
|
34
|
+
app.middleware.insert_after ActionDispatch::Executor, Railswatch::Rails::Middleware
|
|
35
|
+
# look like it works in reverse order?
|
|
36
|
+
app.middleware.insert_before(
|
|
37
|
+
Railswatch::Rails::Middleware,
|
|
38
|
+
Railswatch::Rails::MiddlewareTraceStorerAndCleanup
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if defined?(::Sidekiq)
|
|
42
|
+
require_relative 'gems/sidekiq_ext'
|
|
43
|
+
|
|
44
|
+
Sidekiq.configure_server do |config|
|
|
45
|
+
config.server_middleware do |chain|
|
|
46
|
+
chain.add Railswatch::Gems::SidekiqExt
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
config.on(:startup) do
|
|
50
|
+
if $railswatch_running_mode != :console # rubocop:disable Style/GlobalVars
|
|
51
|
+
# stop web monitoring
|
|
52
|
+
# when we run sidekiq it also starts web monitoring (see above)
|
|
53
|
+
Railswatch._resource_monitor.stop_monitoring
|
|
54
|
+
Railswatch._resource_monitor = nil
|
|
55
|
+
# start background monitoring
|
|
56
|
+
Railswatch._resource_monitor = Railswatch::SystemMonitor::ResourcesMonitor.new(
|
|
57
|
+
ENV['RAILSWATCH_SERVER_CONTEXT'].presence || 'sidekiq',
|
|
58
|
+
ENV['RAILSWATCH_SERVER_ROLE'].presence || 'background'
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if defined?(::Grape)
|
|
66
|
+
require_relative 'gems/grape_ext'
|
|
67
|
+
Railswatch::Gems::GrapeExt.init
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
if defined?(::Delayed::Job)
|
|
71
|
+
require_relative 'gems/delayed_job_ext'
|
|
72
|
+
Railswatch::Gems::DelayedJobExt.init
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
initializer :configure_metrics, after: :initialize_logger do
|
|
77
|
+
next unless Railswatch.enabled
|
|
78
|
+
|
|
79
|
+
ActiveSupport::Notifications.subscribe(
|
|
80
|
+
'process_action.action_controller',
|
|
81
|
+
Railswatch::Instrument::MetricsCollector.new
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
config.after_initialize do
|
|
86
|
+
next unless Railswatch.enabled
|
|
87
|
+
|
|
88
|
+
Railswatch::Models::ApplicationRecord.reset_storage_connection!
|
|
89
|
+
|
|
90
|
+
ActionView::LogSubscriber.prepend Railswatch::Extensions::View
|
|
91
|
+
ActiveRecord::LogSubscriber.prepend Railswatch::Extensions::Db if defined?(ActiveRecord)
|
|
92
|
+
|
|
93
|
+
if defined?(::Rake::Task) && Railswatch.include_rake_tasks
|
|
94
|
+
require_relative 'gems/rake_ext'
|
|
95
|
+
Railswatch::Gems::RakeExt.init
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
if defined?(::Rails::Console)
|
|
100
|
+
$railswatch_running_mode = :console # rubocop:disable Style/GlobalVars
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Railswatch
|
|
4
|
+
module Events
|
|
5
|
+
class Record
|
|
6
|
+
attr_reader :record
|
|
7
|
+
|
|
8
|
+
DEFAULT_COLOR = '#FF00FF'
|
|
9
|
+
DEFAULT_LABEL_COLOR = '#FF00FF'
|
|
10
|
+
DEFAULT_LABEL_ORIENTATION = 'horizontal'
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
def create(name:, datetimei: Time.now.to_i, options: {})
|
|
14
|
+
model = Railswatch::Models::EventRecord.create!(
|
|
15
|
+
name: name,
|
|
16
|
+
options: options,
|
|
17
|
+
occurred_at: Railswatch::Utils.from_datetimei(datetimei.to_i)
|
|
18
|
+
)
|
|
19
|
+
new(model)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def all
|
|
23
|
+
Railswatch::Models::EventRecord.order(:occurred_at).map { |record| new(record) }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def initialize(record)
|
|
28
|
+
@record = record
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
delegate :name, to: :record
|
|
32
|
+
|
|
33
|
+
def datetimei
|
|
34
|
+
record.occurred_at.to_i
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def options
|
|
38
|
+
(record.options || {}).deep_stringify_keys
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def value
|
|
42
|
+
{
|
|
43
|
+
name: name,
|
|
44
|
+
datetime: record.occurred_at,
|
|
45
|
+
datetimei: datetimei,
|
|
46
|
+
options: options
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def to_annotation
|
|
51
|
+
{
|
|
52
|
+
x: datetimei * 1000,
|
|
53
|
+
borderColor: options['borderColor'] || DEFAULT_COLOR,
|
|
54
|
+
label: {
|
|
55
|
+
borderColor: options.dig('label', 'borderColor') || DEFAULT_LABEL_COLOR,
|
|
56
|
+
orientation: options.dig('label', 'orientation') || DEFAULT_LABEL_ORIENTATION,
|
|
57
|
+
text: options.dig('label', 'text') || name
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Railswatch
|
|
4
|
+
module Extensions
|
|
5
|
+
module Db
|
|
6
|
+
# in env
|
|
7
|
+
# this works if config.log_level = :debug
|
|
8
|
+
def sql(event)
|
|
9
|
+
sql_text = event.payload[:sql].to_s
|
|
10
|
+
return super if sql_text.match?(/\brailswatch_/i)
|
|
11
|
+
|
|
12
|
+
CurrentRequest.current.trace({
|
|
13
|
+
group: :db,
|
|
14
|
+
duration: event.duration.round(2),
|
|
15
|
+
sql: sql_text
|
|
16
|
+
})
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Railswatch
|
|
4
|
+
module Gems
|
|
5
|
+
module CustomExtension
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def measure(tag_name, namespace_name = nil, &)
|
|
9
|
+
return yield unless Railswatch.enabled && Railswatch.include_custom_events
|
|
10
|
+
|
|
11
|
+
now = Railswatch::Utils.time
|
|
12
|
+
status = 'success'
|
|
13
|
+
execute_with_status(&)
|
|
14
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
15
|
+
status = 'error'
|
|
16
|
+
raise(e)
|
|
17
|
+
ensure
|
|
18
|
+
save_custom_record(tag_name, namespace_name, status, now)
|
|
19
|
+
CurrentRequest.cleanup
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def execute_with_status
|
|
23
|
+
yield
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def save_custom_record(tag_name, namespace_name, status, now)
|
|
27
|
+
Railswatch::Models::CustomRecord.new(
|
|
28
|
+
tag_name: tag_name,
|
|
29
|
+
namespace_name: namespace_name,
|
|
30
|
+
status: status,
|
|
31
|
+
duration: (Railswatch::Utils.time - now) * 1000,
|
|
32
|
+
datetime: now.strftime(Railswatch::FORMAT),
|
|
33
|
+
datetimei: now.to_i
|
|
34
|
+
).save
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|