builder_apm 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.rspec +2 -0
- data/.travis.yml +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +196 -0
- data/LICENSE.txt +21 -0
- data/README.md +50 -0
- data/Rakefile +28 -0
- data/app/controllers/builder_apm/dashboard_controller.rb +8 -0
- data/app/controllers/builder_apm/error_requests_controller.rb +8 -0
- data/app/controllers/builder_apm/n_plus_one_controller.rb +8 -0
- data/app/controllers/builder_apm/recent_requests_controller.rb +8 -0
- data/app/controllers/builder_apm/request_analysis_controller.rb +8 -0
- data/app/controllers/builder_apm/request_data_controller.rb +41 -0
- data/app/controllers/builder_apm/request_details_controller.rb +9 -0
- data/app/controllers/builder_apm/slow_requests_controller.rb +8 -0
- data/app/controllers/builder_apm/wip_controller.rb +8 -0
- data/app/views/builder_apm/css/_dark.html.erb +119 -0
- data/app/views/builder_apm/css/_main.html.erb +268 -0
- data/app/views/builder_apm/dashboard/index.html.erb +10 -0
- data/app/views/builder_apm/error_requests/index.html.erb +23 -0
- data/app/views/builder_apm/js/_compress.html.erb +93 -0
- data/app/views/builder_apm/js/_dashboard.html.erb +199 -0
- data/app/views/builder_apm/js/_data_fetcher.html.erb +254 -0
- data/app/views/builder_apm/js/_error_requests.html.erb +65 -0
- data/app/views/builder_apm/js/_lzma.html.erb +2670 -0
- data/app/views/builder_apm/js/_n_plus_one.html.erb +79 -0
- data/app/views/builder_apm/js/_recent_requests.html.erb +82 -0
- data/app/views/builder_apm/js/_request_analysis.html.erb +77 -0
- data/app/views/builder_apm/js/_request_details.html.erb +204 -0
- data/app/views/builder_apm/js/_slow_requests.html.erb +74 -0
- data/app/views/builder_apm/n_plus_one/index.html.erb +21 -0
- data/app/views/builder_apm/recent_requests/index.html.erb +21 -0
- data/app/views/builder_apm/request_analysis/index.html.erb +24 -0
- data/app/views/builder_apm/request_details/index.html.erb +7 -0
- data/app/views/builder_apm/shared/_footer.html.erb +3 -0
- data/app/views/builder_apm/shared/_header.html.erb +55 -0
- data/app/views/builder_apm/slow_requests/index.html.erb +21 -0
- data/app/views/builder_apm/wip/index.html.erb +5 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/builder_apm.gemspec +23 -0
- data/config/routes.rb +12 -0
- data/lib/builder_apm/configuration.rb +15 -0
- data/lib/builder_apm/controllers/instrumenter.rb +88 -0
- data/lib/builder_apm/engine.rb +17 -0
- data/lib/builder_apm/methods/instrumenter.rb +79 -0
- data/lib/builder_apm/middleware/timing.rb +56 -0
- data/lib/builder_apm/models/instrumenter.rb +82 -0
- data/lib/builder_apm/railtie.rb +9 -0
- data/lib/builder_apm/redis_client.rb +11 -0
- data/lib/builder_apm/version.rb +3 -0
- data/lib/builder_apm.rb +22 -0
- data/lib/generators/builder_apm/install_generator.rb +21 -0
- data/lib/generators/builder_apm/templates/builder_apm_config.rb +6 -0
- data/lib/generators/builder_apm/templates/create_builder_apm_requests.rb +21 -0
- data/lib/generators/builder_apm/templates/create_builder_apm_sql_queries.rb +17 -0
- metadata +135 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
<%= render 'builder_apm/shared/header' %>
|
2
|
+
|
3
|
+
<table>
|
4
|
+
<thead>
|
5
|
+
<tr>
|
6
|
+
<th class="sortable" data-field="start_time">Time</th>
|
7
|
+
<th class="sortable" data-field="controller">Controller#Action</th>
|
8
|
+
<th class="sortable" data-field="status">Status</th>
|
9
|
+
<th class="sortable" data-field="real_duration_time">Duration (ms)</th>
|
10
|
+
<th class="sortable" data-field="calc_db_runtime">DB Runtime (ms)</th>
|
11
|
+
<th class="sortable" data-field="view_runtime">View Runtime (ms)</th>
|
12
|
+
</tr>
|
13
|
+
</thead>
|
14
|
+
<tbody>
|
15
|
+
<!-- Table content will be populated here by JavaScript -->
|
16
|
+
</tbody>
|
17
|
+
</table>
|
18
|
+
|
19
|
+
<%= render 'builder_apm/js/slow_requests' %>
|
20
|
+
|
21
|
+
<%= render 'builder_apm/shared/footer' %>
|
@@ -0,0 +1,5 @@
|
|
1
|
+
<%= render 'builder_apm/shared/header' %>
|
2
|
+
<div class="image-container">
|
3
|
+
<img src="https://cdn.discordapp.com/attachments/1098245214516297798/1131953429615485020/dripster82_web_page_under_construction_funncy_image_cartoonish_49e72072-6e1e-46ec-a597-2a53bda5eac0.png" />
|
4
|
+
</div>
|
5
|
+
<%= render 'builder_apm/shared/footer' %>
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "builder_apm"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/builder_apm.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative 'lib/builder_apm/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "builder_apm"
|
5
|
+
spec.version = BuilderApm::VERSION
|
6
|
+
spec.authors = ["Paul Ketelle"]
|
7
|
+
spec.email = ["paul.ketelle@builder.ai"]
|
8
|
+
|
9
|
+
spec.summary = %q{Write a short summary, because RubyGems requires one.}
|
10
|
+
spec.description = %q{Write a longer description or delete this line.}
|
11
|
+
# spec.homepage = "http://www.google.com"
|
12
|
+
spec.license = "MIT"
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
14
|
+
spec.add_dependency 'rails', '>= 4.0', '< 8'
|
15
|
+
spec.add_dependency "redis", "~> 4.5"
|
16
|
+
|
17
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
18
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
BuilderApm::Engine.routes.draw do
|
2
|
+
root to: redirect('/builder_apm/dashboard')
|
3
|
+
get 'dashboard', to: 'dashboard#index'
|
4
|
+
get 'request_data', to: 'request_data#index', defaults: { format: 'json' }
|
5
|
+
get 'recent_requests', to: 'recent_requests#index'
|
6
|
+
get 'request_details', to: 'request_details#index'
|
7
|
+
get 'errors_500', to: 'error_requests#index'
|
8
|
+
get 'slow_requests', to: 'slow_requests#index'
|
9
|
+
get 'request_analysis', to: 'request_analysis#index'
|
10
|
+
get 'n_plus_one', to: 'n_plus_one#index'
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module BuilderApm
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :redis_url
|
4
|
+
attr_accessor :enable_controller_profiler
|
5
|
+
attr_accessor :enable_active_record_profiler
|
6
|
+
attr_accessor :enable_methods_profiler
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@redis_url = 'redis://localhost:6379/0'
|
10
|
+
@enable_controller_profiler = true
|
11
|
+
@enable_active_record_profiler = true
|
12
|
+
@enable_methods_profiler = true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module BuilderApm
|
2
|
+
module Controllers
|
3
|
+
class Instrumenter
|
4
|
+
|
5
|
+
def start
|
6
|
+
ActiveSupport::Notifications.subscribe 'start_processing.action_controller' do |*args|
|
7
|
+
event = event_from_args(*args)
|
8
|
+
process_start(event)
|
9
|
+
end
|
10
|
+
|
11
|
+
ActiveSupport::Notifications.subscribe 'process_action.action_controller' do |*args|
|
12
|
+
event = event_from_args(*args)
|
13
|
+
process_action(event)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def process_start(event)
|
18
|
+
return if event.payload[:controller].nil? || event.payload[:controller].start_with?("BuilderApm::")
|
19
|
+
|
20
|
+
uuid = event.payload[:headers].env['action_dispatch.request_id']
|
21
|
+
Thread.current[:request_id] = uuid
|
22
|
+
end
|
23
|
+
|
24
|
+
def process_action(event)
|
25
|
+
return if event.payload[:controller].start_with?("BuilderApm::")
|
26
|
+
|
27
|
+
request_id = Thread.current[:request_id]
|
28
|
+
Thread.current['request_data'] = extract_data_from_event(event)
|
29
|
+
ensure
|
30
|
+
clean_up_thread_values
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def event_from_args(*args)
|
36
|
+
data = args.extract_options!
|
37
|
+
ActiveSupport::Notifications::Event.new(*args, data)
|
38
|
+
end
|
39
|
+
|
40
|
+
def extract_data_from_event(event)
|
41
|
+
data = {
|
42
|
+
request_id: Thread.current[:request_id],
|
43
|
+
controller: event.payload[:controller],
|
44
|
+
action: event.payload[:action],
|
45
|
+
params: event.payload[:params].except(:controller, :action),
|
46
|
+
path: event.payload[:path],
|
47
|
+
format: event.payload[:format],
|
48
|
+
method: event.payload[:method],
|
49
|
+
controller_line: file_and_line_number(event.payload[:controller], event.payload[:action]),
|
50
|
+
status: event.payload[:status],
|
51
|
+
start_time: event.time,
|
52
|
+
end_time: event.end,
|
53
|
+
duration: event.duration,
|
54
|
+
db_runtime: event.payload[:db_runtime],
|
55
|
+
view_runtime: event.payload[:view_runtime],
|
56
|
+
stack: Thread.current[:stack]
|
57
|
+
}
|
58
|
+
if event.payload[:exception]
|
59
|
+
exception_class, exception_message = event.payload[:exception]
|
60
|
+
data[:exception_class] = exception_class
|
61
|
+
data[:exception_message] = exception_message
|
62
|
+
data[:exception_backtrace] = event.payload[:exception_object].backtrace #.select { |line| line.start_with?(Rails.root.to_s) }
|
63
|
+
end
|
64
|
+
|
65
|
+
data
|
66
|
+
end
|
67
|
+
|
68
|
+
def clean_up_thread_values
|
69
|
+
Thread.current[:sql_event_id] = nil
|
70
|
+
Thread.current[:request_id] = nil
|
71
|
+
Thread.current[:stack] = nil
|
72
|
+
end
|
73
|
+
|
74
|
+
def file_and_line_number(controller, action)
|
75
|
+
begin
|
76
|
+
controller_class = controller.constantize
|
77
|
+
method_info = controller_class.instance_method(action)
|
78
|
+
absolute_path = method_info.source_location.first
|
79
|
+
line_number = method_info.source_location.last
|
80
|
+
relative_path = Pathname.new(absolute_path).relative_path_from(Rails.root)
|
81
|
+
"#{relative_path}:#{line_number}"
|
82
|
+
rescue NameError
|
83
|
+
"???"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module BuilderApm
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace BuilderApm
|
4
|
+
|
5
|
+
initializer 'builder_apm.start' do |app|
|
6
|
+
BuilderApm::Controllers::Instrumenter.new.start if BuilderApm.configuration.enable_controller_profiler
|
7
|
+
BuilderApm::Models::Instrumenter.start if BuilderApm.configuration.enable_active_record_profiler
|
8
|
+
BuilderApm::Methods::Instrumenter.new.start if BuilderApm.configuration.enable_methods_profiler
|
9
|
+
end
|
10
|
+
|
11
|
+
config.after_initialize do
|
12
|
+
Rails.application.routes.append do
|
13
|
+
mount BuilderApm::Engine => '/builder_apm'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module BuilderApm
|
2
|
+
module Methods
|
3
|
+
class Instrumenter
|
4
|
+
def initialize(root_path: Rails.root.to_s)
|
5
|
+
@root_path = root_path
|
6
|
+
@call_times = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def start
|
10
|
+
@trace = setup_trace
|
11
|
+
@trace.enable
|
12
|
+
end
|
13
|
+
|
14
|
+
def stop
|
15
|
+
@trace.disable unless @trace.nil?
|
16
|
+
end
|
17
|
+
|
18
|
+
def setup_trace
|
19
|
+
me = self
|
20
|
+
TracePoint.new(:call, :return) do |tp|
|
21
|
+
me.process_trace_point(tp) if me.valid_trace_point?(tp)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def valid_trace_point?(tp)
|
26
|
+
!Thread.current[:request_id].nil? && tp.path.start_with?(@root_path)
|
27
|
+
end
|
28
|
+
|
29
|
+
def process_trace_point(tp)
|
30
|
+
if tp.event == :call
|
31
|
+
process_call_event(tp)
|
32
|
+
elsif tp.event == :return
|
33
|
+
process_return_event(tp)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def process_call_event(tp)
|
40
|
+
method_id = "#{tp.defined_class}##{tp.method_id}"
|
41
|
+
@call_times[method_id] = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
42
|
+
caller_info = caller_locations(4,1).first
|
43
|
+
calling_file_path = caller_info.absolute_path
|
44
|
+
calling_line_number = caller_info.lineno
|
45
|
+
|
46
|
+
method_call = {
|
47
|
+
method: method_id,
|
48
|
+
method_line: "#{tp.path.gsub(@root_path, '')}:#{tp.lineno}",
|
49
|
+
triggering_line: "#{calling_file_path.gsub(@root_path, '')}:#{calling_line_number}",
|
50
|
+
children: [],
|
51
|
+
start_time: Time.now.to_f * 1000,
|
52
|
+
sql_events: []
|
53
|
+
}
|
54
|
+
|
55
|
+
(Thread.current[:stack] ||= []).push(method_call)
|
56
|
+
end
|
57
|
+
|
58
|
+
def process_return_event(tp)
|
59
|
+
method_id = "#{tp.defined_class}##{tp.method_id}"
|
60
|
+
|
61
|
+
if @call_times.key?(method_id)
|
62
|
+
elapsed_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - @call_times[method_id]
|
63
|
+
elapsed_time_in_ms = (elapsed_time * 1000).round(3)
|
64
|
+
@call_times.delete(method_id)
|
65
|
+
|
66
|
+
method_call = (Thread.current[:stack] ||= []).pop
|
67
|
+
method_call[:end_time] = Time.now.to_f * 1000
|
68
|
+
method_call[:duration] = elapsed_time_in_ms
|
69
|
+
|
70
|
+
if Thread.current[:stack]&.any?
|
71
|
+
Thread.current[:stack].last[:children].push(method_call)
|
72
|
+
else
|
73
|
+
Thread.current[:stack].push(method_call)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module BuilderApm
|
2
|
+
module Middleware
|
3
|
+
class Timing
|
4
|
+
def initialize(app, redis_client: BuilderApm::RedisClient.client)
|
5
|
+
@redis_client = redis_client
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
request_id = env["action_dispatch.request_id"]
|
11
|
+
Thread.current["request_id"] = request_id
|
12
|
+
start_time = Time.now.to_f * 1000
|
13
|
+
|
14
|
+
begin
|
15
|
+
result = @app.call(env)
|
16
|
+
rescue => e
|
17
|
+
handle_exception(e, start_time, request_id)
|
18
|
+
raise e
|
19
|
+
end
|
20
|
+
|
21
|
+
end_time = Time.now.to_f * 1000
|
22
|
+
handle_timing(start_time, end_time, request_id)
|
23
|
+
|
24
|
+
result
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def handle_timing(start_time, end_time, request_id)
|
30
|
+
duration = end_time - start_time;
|
31
|
+
data = Thread.current['request_data']
|
32
|
+
|
33
|
+
if data
|
34
|
+
data[:real_start_time] = start_time
|
35
|
+
data[:real_end_time] = end_time
|
36
|
+
data[:real_duration_time] = end_time - start_time
|
37
|
+
Thread.current['request_data'] = nil
|
38
|
+
|
39
|
+
@redis_client.zadd("builder_apm:timestamps", end_time, request_id)
|
40
|
+
@redis_client.set("builder_apm:Request:#{data[:request_id]}", data.to_json)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def handle_exception(e, start_time, request_id)
|
45
|
+
end_time = Time.now.to_f * 1000
|
46
|
+
|
47
|
+
data = Thread.current['request_data'] || {}
|
48
|
+
data[:exception_class] = e.class.to_s
|
49
|
+
data[:exception_message] = e.message
|
50
|
+
data[:exception_backtrace] = e.backtrace
|
51
|
+
|
52
|
+
handle_timing(start_time, end_time, request_id)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module BuilderApm
|
2
|
+
module Models
|
3
|
+
class Instrumenter
|
4
|
+
def self.start
|
5
|
+
new.subscribe_to_notifications
|
6
|
+
end
|
7
|
+
|
8
|
+
def subscribe_to_notifications
|
9
|
+
ActiveSupport::Notifications.subscribe('sql.active_record') { |*args| handle_sql_active_record(*args) }
|
10
|
+
ActiveSupport::Notifications.subscribe('instantiation.active_record') { |*args| handle_instantiation_active_record(*args) }
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def handle_sql_active_record(*args)
|
16
|
+
data = args.extract_options!
|
17
|
+
event = ActiveSupport::Notifications::Event.new(*args, data)
|
18
|
+
name = event.payload[:name]
|
19
|
+
return if name == "SCHEMA" || Thread.current[:request_id].nil?
|
20
|
+
|
21
|
+
triggering_line = determine_triggering_line(caller)
|
22
|
+
|
23
|
+
sql_query_data = build_sql_query_data(event, triggering_line)
|
24
|
+
store_sql_query_data(sql_query_data)
|
25
|
+
end
|
26
|
+
|
27
|
+
def handle_instantiation_active_record(*args)
|
28
|
+
data = args.extract_options!
|
29
|
+
event = ActiveSupport::Notifications::Event.new(*args, data)
|
30
|
+
|
31
|
+
update_last_sql_query_data_with_instantiation_info(event)
|
32
|
+
end
|
33
|
+
|
34
|
+
def determine_triggering_line(call_stack)
|
35
|
+
app_stack = call_stack.select { |line| line.include?(Rails.root.to_s) }
|
36
|
+
app_stack.first.to_s.gsub(Rails.root.to_s, '')
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_sql_query_data(event, triggering_line)
|
40
|
+
{
|
41
|
+
request_id: Thread.current[:request_id],
|
42
|
+
sql_id: SecureRandom.uuid,
|
43
|
+
sql: event.payload[:sql],
|
44
|
+
params: event.payload[:binds].map { |a| a.value },
|
45
|
+
triggering_line: triggering_line,
|
46
|
+
name: event.payload[:name],
|
47
|
+
cached: event.payload[:cached] || false,
|
48
|
+
start_time: event.time,
|
49
|
+
end_time: event.end,
|
50
|
+
duration: event.duration,
|
51
|
+
record_count: 0,
|
52
|
+
class_name: ''
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def store_sql_query_data(sql_query_data)
|
57
|
+
Thread.current[:sql_event_id] = sql_query_data[:sql_id]
|
58
|
+
|
59
|
+
if Thread.current[:stack]&.any?
|
60
|
+
Thread.current[:stack].last[:sql_events].push(sql_query_data)
|
61
|
+
else
|
62
|
+
(Thread.current[:stack] ||= []).push({sql_events: [sql_query_data], children: []})
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def update_last_sql_query_data_with_instantiation_info(event)
|
67
|
+
stack = Thread.current[:stack]
|
68
|
+
request_id = Thread.current[:request_id]
|
69
|
+
|
70
|
+
return if stack.nil? || stack.empty? || request_id.nil?
|
71
|
+
|
72
|
+
last_sql = Thread.current[:stack].last[:sql_events].pop
|
73
|
+
if last_sql[:sql_id] == Thread.current[:sql_event_id]
|
74
|
+
last_sql[:record_count] = event.payload[:record_count]
|
75
|
+
last_sql[:class_name] = event.payload[:class_name]
|
76
|
+
end
|
77
|
+
Thread.current[:stack].last[:sql_events].push(last_sql)
|
78
|
+
Thread.current[:sql_event_id] = nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/builder_apm.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require "builder_apm/version"
|
2
|
+
require 'builder_apm/controllers/instrumenter'
|
3
|
+
require 'builder_apm/models/instrumenter'
|
4
|
+
require 'builder_apm/methods/instrumenter'
|
5
|
+
require 'builder_apm/middleware/timing'
|
6
|
+
require 'builder_apm/configuration'
|
7
|
+
require 'builder_apm/engine'
|
8
|
+
require 'builder_apm/redis_client'
|
9
|
+
require 'builder_apm/railtie'
|
10
|
+
|
11
|
+
module BuilderApm
|
12
|
+
def self.configure
|
13
|
+
yield(configuration)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.configuration
|
17
|
+
@configuration ||= Configuration.new
|
18
|
+
end
|
19
|
+
|
20
|
+
class Error < StandardError; end
|
21
|
+
# Your code goes here...
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module BuilderApm
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
include Rails::Generators::Migration
|
5
|
+
source_root File.expand_path('templates', __dir__)
|
6
|
+
|
7
|
+
def self.next_migration_number(dirname)
|
8
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
9
|
+
end
|
10
|
+
|
11
|
+
def copy_migrations
|
12
|
+
migration_template "create_builder_apm_requests.rb", "db/migrate/create_builder_apm_requests.rb"
|
13
|
+
migration_template "create_builder_apm_sql_queries.rb", "db/migrate/create_builder_apm_sql_queries.rb"
|
14
|
+
end
|
15
|
+
|
16
|
+
def copy_initializer_file
|
17
|
+
copy_file 'builder_apm_config.rb', 'config/initializers/builder_apm.rb'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class CreateBuilderApmRequests < ActiveRecord::Migration[6.0]
|
2
|
+
def change
|
3
|
+
create_table :builder_apm_requests do |t|
|
4
|
+
t.string :request_url
|
5
|
+
t.string :request_method
|
6
|
+
t.string :controller
|
7
|
+
t.string :action
|
8
|
+
t.text :params
|
9
|
+
t.integer :status
|
10
|
+
t.string :format
|
11
|
+
t.string :file_and_line_number
|
12
|
+
t.float :duration
|
13
|
+
t.float :view_runtime
|
14
|
+
t.float :db_runtime
|
15
|
+
t.datetime :start_time
|
16
|
+
t.datetime :end_time
|
17
|
+
|
18
|
+
t.timestamps
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class CreateBuilderApmSqlQueries < ActiveRecord::Migration[6.0]
|
2
|
+
def change
|
3
|
+
create_table :builder_apm_sql_queries do |t|
|
4
|
+
t.string :event_uuid
|
5
|
+
t.text :sql
|
6
|
+
t.text :params
|
7
|
+
t.float :duration
|
8
|
+
t.integer :result_count
|
9
|
+
t.string :class_name
|
10
|
+
t.boolean :rails_cached
|
11
|
+
t.datetime :start_time
|
12
|
+
t.datetime :end_time
|
13
|
+
|
14
|
+
t.timestamps
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|